How To Handle Callback Using Kotlin Coroutines
Solution 1:
Since the Firebase Realtime Database SDK doesn't provide any suspend functions, coroutines are not helpful when dealing with its APIs. You would need to convert the callback into a suspend function in order for you to be able to await the result in a coroutine.
Here's a suspend extension function that does this (I discovered a solution it by doing a google search):
suspendfun DatabaseReference.getValue(): DataSnapshot {
return async(CommonPool) {
suspendCoroutine<DataSnapshot> { continuation ->
addListenerForSingleValueEvent(FValueEventListener(
onDataChange = { continuation.resume(it) },
onError = { continuation.resumeWithException(it.toException()) }
))
}
}.await()
}
classFValueEventListener(val onDataChange: (DataSnapshot) -> Unit, val onError: (DatabaseError) -> Unit) : ValueEventListener {
overridefunonDataChange(data: DataSnapshot) = onDataChange.invoke(data)
overridefunonCancelled(error: DatabaseError) = onError.invoke(error)
}
With this, you now how a getValue()
suspect method on DatabaseReference that can be awaited in a coroutine.
Solution 2:
The @Doug example for singleValueEvent if you want to keep listing you can use coroutine flow like below:
@ExperimentalCoroutinesApiinlinefun<reified T> DatabaseReference.listen(): Flow<DataResult<T?>> =
callbackFlow {
val valueListener = object : ValueEventListener {
overridefunonCancelled(databaseError: DatabaseError) {
close(databaseError.toException())
}
overridefunonDataChange(dataSnapshot: DataSnapshot) {
try {
val value = dataSnapshot.getValue(T::class.java)
offer(DataResult.Success(value))
} catch (exp: Exception) {
Timber.e(exp)
if (!isClosedForSend) offer(DataResult.Error(exp))
}
}
}
addValueEventListener(valueListener)
awaitClose { removeEventListener(valueListener) }
}
Solution 3:
In case anyone still uses the original answer's code but needs to update it to match the non-experimental version of Coroutines
here's how I changed it:
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.ValueEventListener
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
suspendfun DatabaseReference.getSnapshotValue(): DataSnapshot {
return withContext(Dispatchers.IO) {
suspendCoroutine<DataSnapshot> { continuation ->
addListenerForSingleValueEvent(FValueEventListener(
onDataChange = { continuation.resume(it) },
onError = { continuation.resumeWithException(it.toException()) }
))
}
}
}
classFValueEventListener(val onDataChange: (DataSnapshot) -> Unit, val onError: (DatabaseError) -> Unit) : ValueEventListener {
overridefunonDataChange(data: DataSnapshot) = onDataChange.invoke(data)
overridefunonCancelled(error: DatabaseError) = onError.invoke(error)
}
Then using it would be as simple as: val snapshot = ref.getSnapshotValue()
Update
I also needed to observe a node and used Omar's answer to do it. If anyone needs an example of how to use it here it is:
@ExperimentalCoroutinesApiinlinefun<reified T> DatabaseReference.listen(): Flow<T?>? =
callbackFlow {
val valueListener = object : ValueEventListener {
overridefunonCancelled(databaseError: DatabaseError) {
close()
}
overridefunonDataChange(dataSnapshot: DataSnapshot) {
try {
val value = dataSnapshot.getValue(T::class.java)
offer(value)
} catch (exp: Exception) {
if (!isClosedForSend) offer(null)
}
}
}
addValueEventListener(valueListener)
awaitClose { removeEventListener(valueListener) }
}
Then to call it inside an Activity or Fragment you would create your listener like so:
var listener = FirebaseUtils.databaseReference
.child(AppConstants.FIREBASE_PATH_EMPLOYEES)
.child(AuthUtils.retrieveUID()!!).listen<User>()
Then call it inside your function:
CoroutineScope(IO).launch {
withContext(IO) {
listener?.collect{
print(it)
}
}
}
And then dispose inside onStop()
:
overridefunonStop(){
listener = nullsuper.onStop()
}
Post a Comment for "How To Handle Callback Using Kotlin Coroutines"