Skip to content Skip to sidebar Skip to footer

How To Handle Callback Using Kotlin Coroutines

the following snippet returns the result as 'null' on sequential code flow. I understand coroutines could be a viable solution to handle the callback asynchronously. fun getUs

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"