
This post is very similar to previous; how to implements remote config.
If you arrived here I hope this post will be useful.
First I will be show how to comunicate with Realtime Database – Firebase;
This communication will works with inputs e outputs, other words reading and writing.
I hope you have minimal know em native platforms (Android and iOS); remember this post I don’t use libs what will be facilitate to implementation Firebase Realtime Database.
Let’s go; I´m going this post in three parts.
– Native implementation (basic configs)
– KMP implementation
– Native implementation (iOS configs)
Native implementation
For the natives platform follow the documentation official.
https://firebase.google.com/docs/remote-config/get-started
Also lets go use dependency injection in this project.
The dependency injection used is Koin; follow the official documentation.
https://insert-koin.io/docs/reference/koin-mp/kmp
KMP implementation
Let’s remember how to organize the project KMP.
-Project
-Project/ComposeApp
-Project/ComposeApp/androidMain
-Project/ComposeApp/commonMain
-Project/ComposeApp/iosMain
-Project/iosApp/iosApp
These are the main classes to be created in the project.
-Project/ComposeApp/androidMain/pack/FirebaseDataBaseRealTimeBridge.android.kt
-Project/ComposeApp/androidMain/pack/KoinInit.kt
-Project/ComposeApp/androidMain/pack/KoinModuleAndroid.kt
-Project/ComposeApp/commonMain/pack/FirebaseDataBaseRealTimeBridge.kt
-Project/ComposeApp/commonMain/pack/FirebaseRealTimeDataBase.kt
-Project/ComposeApp/commonMain/pack/KoinInit.kt
-Project/ComposeApp/commonMain/pack/KoinModule.kt
-Project/ComposeApp/commonMain/pack/FirebaseRealTimeDataBaseScreen.kt
-Project/ComposeApp/commonMain/pack/FirebaseRealTimeDataBaseViewModel.kt
-Project/ComposeApp/androidMain/pack/FirebaseDataBaseRealTimeBridge.ios.kt
-Project/ComposeApp/androidMain/pack/KoinInit.kt
-Project/ComposeApp/androidMain/pack/KoinModuleAndroid.kt
-Project/iosApp/iosApp/SwiftFirebaseDataBaseRealTime.swift
We will create the following class inside “Project/ComposeApp/androidMain”:
Each with their own responsibility. This class will do communication with Firebase.
Which later will do implements by ViewModel.
actual class FirebaseDataBaseRealTimeBridge : FirebaseRealTimeDataBase {
private val myFireBaseDataBase: FirebaseDatabase = Firebase.database
actual override fun fetchDataNodo(nodoName: String,
onSuccess: (String) -> Unit,
onError: (Exception) -> Unit) {
myFireBaseDataBase.getReference( nodoName)
.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
onSuccess(snapshot.toString())
}
override fun onCancelled(error: DatabaseError) {
onCancelled(error)
}
})
}
actual override fun putDataByNodoName(nodoName: String, keyNewNodo: String, valueForNodo: String) {
val newValues = mapOf(
keyNewNodo to valueForNodo
)
myFireBaseDataBase.getReference(nodoName)
.updateChildren(newValues)
.addOnSuccessListener { }
.addOnFailureListener { error ->
throw Exception("Fail save")
}
}
}actual class KoinInit() {
constructor(androidContext: Context) : this() {
initKoin(androidContext)
}
fun initKoin(androidContext: Context) {
val modules = sharedModules() + moduleAndroid()
startKoin {
androidLogger()
androidContext(androidContext = androidContext)
modules(modules = modules)
}
}
}fun moduleAndroid() = module {
single<FirebaseRealTimeDataBase> { FirebaseDataBaseRealTimeBridge() }
factory { FirebaseRealTimeDataBaseViewModel(get<FirebaseRealTimeDataBase>() as FirebaseDataBaseRealTimeBridge) }
}We will create the following class inside “Project/ComposeApp/iosMain”:
actual class FirebaseDataBaseRealTimeBridge(private val delegateFirebaseRealTimeDataBase: FirebaseRealTimeDataBase) : FirebaseRealTimeDataBase {
actual override fun fetchDataNodo(
nodoName: String,
onSuccess: (String) -> Unit,
onError: (Exception) -> Unit,
) {
delegateFirebaseRealTimeDataBase.fetchDataNodo(nodoName, onSuccess, onError)
}
actual override fun putDataByNodoName(
nodoName: String,
keyNewNodo: String,
valueForNodo: String,
) {
delegateFirebaseRealTimeDataBase.putDataByNodoName(nodoName = nodoName, keyNewNodo = keyNewNodo, valueForNodo = valueForNodo)
}
}Don’t forget to configure dependency injection in the iOS; follow the examples below:
@OptIn(ExperimentalObjCName::class, BetaInteropApi::class)
@ObjCName(name = "KoinInit", exact = true)
@ExportObjCClass
actual class KoinInit() {
fun initKoin(delegateFirebaseRemoteConfigs: FirebaseRemoteConfigs,
delegateFirebaseRealTimeDataBase: FirebaseRealTimeDataBase) {
val modules = sharedModules() + moduleIos(delegateFirebaseRemoteConfigs, delegateFirebaseRealTimeDataBase)
startKoin {
modules(modules = modules)
}.also {
koinInstance = it
}
}
}Note: one passed as a parameter cames iOS native;
fun moduleIos(delegateFirebaseRealTimeDataBase: FirebaseRealTimeDataBase) = module {
single<FirebaseRealTimeDataBase> { FirebaseDataBaseRealTimeBridge(delegateFirebaseRealTimeDataBase) }
factory { FirebaseRealTimeDataBaseViewModel(get<FirebaseRealTimeDataBase>() as FirebaseDataBaseRealTimeBridge) }
}We will create the following class inside “Project/ComposeApp/commonMain”:
expect class KoinInitfun sharedModules() = module { } expect class FirebaseDataBaseRealTimeBridge: FirebaseRealTimeDataBase {
override fun fetchDataNodo(
nodoName: String,
onSuccess: (String) -> Unit,
onError: (Exception) -> Unit,
)
override fun putDataByNodoName(nodoName: String, keyNewNodo: String, valueForNodo: String)
}interface FirebaseRealTimeDataBase {
fun fetchDataNodo(nodoName: String = "messages",
onSuccess: (String) -> Unit,
onError: (Exception) -> Unit)
fun putDataByNodoName(nodoName: String, keyNewNodo: String, valueForNodo: String)
}class FirebaseRealTimeDataBaseViewModel(
private val firebaseDataBaseRealTimeBridge: FirebaseDataBaseRealTimeBridge
): BaseViewModel() {
private val _nodo = MutableStateFlow<String>( "messages")
val nodo: StateFlow<String> = _nodo
private val _stateSnapShotFiebaseDataBaseRealTimeByNodo = MutableStateFlow<GetRealTimeDataBaseUiState>(GetRealTimeDataBaseUiState.Loading(step = ""))
val stateSnapShotFiebaseDataBaseRealTimeByNodo: StateFlow<GetRealTimeDataBaseUiState> = _stateSnapShotFiebaseDataBaseRealTimeByNodo
var jobGetSnaptShotFirebaseDataBaseRealTime: Job? = SupervisorJob()
private val _statePutFiebaseDataBaseRealTimeByNodo = MutableStateFlow<PutRealTimeDataBaseUiState>(PutRealTimeDataBaseUiState.Idle)
val statePutFiebaseDataBaseRealTimeByNodo: StateFlow<PutRealTimeDataBaseUiState> = _statePutFiebaseDataBaseRealTimeByNodo
var jobPutDataFirebaseDataBaseRealTime: Job? = SupervisorJob()
init {
viewModelScope.launch {
getSnapShotFirebaseDataBaseRealTime(nodoName = nodo.value)
}
}
fun putDataFirebaseDataBaseRealTime(nodoName: String, keyNewNodo: String, valueForNodo: String){
if(keyNewNodo.isEmpty() || valueForNodo.isEmpty() ){
return
}
jobPutDataFirebaseDataBaseRealTime?.cancel()
jobPutDataFirebaseDataBaseRealTime = viewModelScope.launch {
try {
if(nodoName != nodo.value){
_nodo.value = nodoName
async {
getSnapShotFirebaseDataBaseRealTime(nodoName = nodoName)
}.await()
}
delay(timeMillis = 1000)
async {
firebaseDataBaseRealTimeBridge.putDataByNodoName(nodoName = nodoName, keyNewNodo = keyNewNodo, valueForNodo = valueForNodo)
}.await()
delay(timeMillis = 100)
}catch (exception: Exception){
_statePutFiebaseDataBaseRealTimeByNodo.value = PutRealTimeDataBaseUiState.Error(exception.message.toString())
delay(timeMillis = 100)
}
}
}
fun getSnapShotFirebaseDataBaseRealTime(nodoName: String) {
jobGetSnaptShotFirebaseDataBaseRealTime?.cancel()
jobGetSnaptShotFirebaseDataBaseRealTime = viewModelScope.launch {
try {
async {
firebaseDataBaseRealTimeBridge.fetchDataNodo(
nodoName = nodoName,
onSuccess = { it ->
_stateSnapShotFiebaseDataBaseRealTimeByNodo.value = GetRealTimeDataBaseUiState.Success(it)
},
onError = { exception ->
_stateSnapShotFiebaseDataBaseRealTimeByNodo.value = GetRealTimeDataBaseUiState.Error(exception.message.toString())
}
)
}.await()
}catch (exception: Exception){
_stateSnapShotFiebaseDataBaseRealTimeByNodo.value = GetRealTimeDataBaseUiState.Error(exception.message.toString())
delay(timeMillis = 100)
}
}
}
sealed class GetRealTimeDataBaseUiState {
data class Loading(val step: String = "loading") : GetRealTimeDataBaseUiState()
data class Error(val message: String = "") : GetRealTimeDataBaseUiState()
data class Success(val valueSnapShot: String = "") : GetRealTimeDataBaseUiState()
}
sealed class PutRealTimeDataBaseUiState {
object Idle : PutRealTimeDataBaseUiState()
data class Error(val message: String = "") : PutRealTimeDataBaseUiState()
}
}@Composable
fun FirebaseRealTimeDataBaseScreen(
firebaseRealTimeDataBaseViewModel: FirebaseRealTimeDataBaseViewModel = koinInject<FirebaseRealTimeDataBaseViewModel>()) {
var snapShotNodoValue: String by remember { mutableStateOf("") }
var nodoValue: String by remember { mutableStateOf(firebaseRealTimeDataBaseViewModel.nodo.value ) }
var keyByKeyNodo: String by remember { mutableStateOf("") }
var valueByKeyNodo: String by remember { mutableStateOf("") }
val stateRealTimeDataBaseViewModel: FirebaseRealTimeDataBaseViewModel.GetRealTimeDataBaseUiState by firebaseRealTimeDataBaseViewModel.stateSnapShotFiebaseDataBaseRealTimeByNodo.collectAsState()
snapShotNodoValue = when(stateRealTimeDataBaseViewModel){
is FirebaseRealTimeDataBaseViewModel.GetRealTimeDataBaseUiState.Error -> {
(stateRealTimeDataBaseViewModel as FirebaseRealTimeDataBaseViewModel.GetRealTimeDataBaseUiState.Error).message
}
is FirebaseRealTimeDataBaseViewModel.GetRealTimeDataBaseUiState.Loading -> {
(stateRealTimeDataBaseViewModel as FirebaseRealTimeDataBaseViewModel.GetRealTimeDataBaseUiState.Loading).step
}
is FirebaseRealTimeDataBaseViewModel.GetRealTimeDataBaseUiState.Success -> {
(stateRealTimeDataBaseViewModel as FirebaseRealTimeDataBaseViewModel.GetRealTimeDataBaseUiState.Success).valueSnapShot
}
}
var errorMessagePut: String by remember { mutableStateOf("") }
val statePutRealTimeDataBaseViewModel: FirebaseRealTimeDataBaseViewModel.PutRealTimeDataBaseUiState by firebaseRealTimeDataBaseViewModel.statePutFiebaseDataBaseRealTimeByNodo.collectAsState()
errorMessagePut = when (statePutRealTimeDataBaseViewModel){
is FirebaseRealTimeDataBaseViewModel.PutRealTimeDataBaseUiState.Error -> {
(statePutRealTimeDataBaseViewModel as FirebaseRealTimeDataBaseViewModel.PutRealTimeDataBaseUiState.Error) .message
}
FirebaseRealTimeDataBaseViewModel.PutRealTimeDataBaseUiState.Idle -> {
""
}
}
DisposableEffect(Unit) {
onDispose {
}
}
RegisterBackHandler {
firebaseRealTimeDataBaseViewModel.clear()
}
MaterialTheme {
Column(
modifier = Modifier
.safeContentPadding()
.fillMaxSize().padding(4.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
TopAppBar(
actions = {
IconButton(onClick = {
}) {
Icon(
imageVector = Icons.Default.BackHand,
contentDescription = "Localized description"
)
}
},
navigationIcon = {
IconButton(onClick = { }) {
Icon(
imageVector = Icons.Default.CloudOff,
contentDescription = "Localized description"
)
}
},
title = { Text("List teach") }
)
Card(
modifier = Modifier.fillMaxWidth().border(
width = 2.dp,
color = AppColors.blueDark,
shape = RoundedCornerShape(16.dp)
),
shape = RoundedCornerShape(20.dp),
colors = CardDefaults.cardColors(
containerColor = AppColors.blueLight,
contentColor = AppColors.blueNormal
),
elevation = CardDefaults.cardElevation()
) {
Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
Spacer(Modifier.height(8.dp))
Text(text = "Get all infos nodo -> $nodoValue", color = AppColors.blackNormal)
Text(text = "HERE -> $snapShotNodoValue", color = AppColors.blackNormal)
Spacer(Modifier.height(60.dp))
Text(text = "Add $errorMessagePut", color = AppColors.blackNormal)
Spacer(Modifier.height(10.dp))
Text(text = "Nodo", color = AppColors.blackNormal)
BasicTextField(
maxLines = 1,
textStyle = TextStyle.Default.copy(
fontSize = 30.sp,
color = Color.DarkGray
),
value = nodoValue,
onValueChange = { nodoValue = it },
modifier = Modifier
.padding(8.dp)
.border(2.dp, Color.Black)
.background(Color.White)
.fillMaxWidth()
.height(40.dp)
)
Spacer(Modifier.height(10.dp))
Text(text = "key", color = AppColors.blackNormal)
BasicTextField(
maxLines = 1,
textStyle = TextStyle.Default.copy(
fontSize = 30.sp,
color = Color.DarkGray
),
value = keyByKeyNodo,
onValueChange = { keyByKeyNodo = it },
modifier = Modifier
.padding(8.dp)
.border(2.dp, Color.Black)
.background(Color.White)
.fillMaxWidth()
.height(40.dp)
)
Spacer(Modifier.height(10.dp))
Text(text = "value", color = AppColors.blackNormal)
BasicTextField(
maxLines = 1,
textStyle = TextStyle.Default.copy(
fontSize = 30.sp,
color = Color.DarkGray
),
value = valueByKeyNodo,
onValueChange = { valueByKeyNodo = it },
modifier = Modifier
.padding(8.dp)
.border(2.dp, Color.Black)
.background(Color.White)
.fillMaxWidth()
.height(40.dp)
)
Spacer(Modifier.height(10.dp))
Button(onClick = {
firebaseRealTimeDataBaseViewModel.putDataFirebaseDataBaseRealTime(
nodoName = nodoValue,
keyNewNodo = keyByKeyNodo,
valueForNodo = valueByKeyNodo,
)
}) {
Text(text = "Click add value", color = AppColors.whiteNormal)
}
}
}
Spacer(Modifier.height(20.dp))
}
}
}Native implementation (iOS configs)
Project/iosApp/iosApp
Now let’s work on the iOS part; where the firebase class value will be sent as a parameter.
class AppDelegate: NSObject, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
FirebaseApp.configure()
let swiftFirebaseDataBaseRealTime = SwiftFirebaseDataBaseRealTime()
ComposeApp.KoinInit().doInitKoin(
delegateFirebaseRealTimeDataBase: swiftFirebaseDataBaseRealTime
)
return true
}
}
@main
struct iOSApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}import FirebaseDatabase
import ComposeApp
@objc public class SwiftFirebaseDataBaseRealTime: NSObject, FirebaseRealTimeDataBase {
let myFireBaseDataBase = Database.database()
@objc public func fetchDataNodo(nodoName: String,
onSuccess: @escaping (String) -> Void,
onError: @escaping (KotlinException) -> Void) {
let reference = myFireBaseDataBase.reference(withPath: nodoName)
reference.observe(.value) { snapshot in
print("fetchDataNodo: Value snapshot is: \(snapshot)")
onSuccess(snapshot.description)
} withCancel: { error in
print("fetchDataNodo: Cancelled with error: \(error.localizedDescription)")
onError(error as! KotlinException)
}
}
@objc public func putDataByNodoName(nodoName: String, keyNewNodo: String, valueForNodo: String) {
let values: [String: String] = [
keyNewNodo: valueForNodo
]
let reference = myFireBaseDataBase.reference(withPath: nodoName)
reference.updateChildValues(values) { error, _ in
if let error = error {
print("putDataByNodoName: Fail \(error.localizedDescription)")
// Se quiser lançar, pode usar uma completion ou delegate
} else {
print("putDataByNodoName: Success \(nodoName)")
}
}
}
}Extras
You can access also ours GitHub with all code and news features;
Don’t forget habilitate write and reading in your firebase console.
If you have doubts, question don’t hesitate to ask; Will be a pleasure to help.
