firestore, coroutine and flow

Viewed 37

firebase method is working on worker thread automatically. but I have used coroutine and callbackflow to implement firebase listener code synchronously or get return from the listener. below is my code that I explained

coroutine await with firebase for one shot

override suspend fun checkNickName(nickName: String): Results<Int> {


    lateinit var result : Results<Int>

    fireStore.collection("database")
        .document("user")
        .get()
        .addOnCompleteListener { document ->


            if (document.isSuccessful) {

                val list = document.result.data?.get("nickNameList") as List<String>

                if (list.contains(nickName))
                    result = Results.Exist(1)
                else
                    result = Results.No(0)

                //document.getResult().get("nickNameList")
            }
            else {

            }
        }.await()

    return result
}

callbackflow with firebase listener

override fun getOwnUser(): Flow<UserEntity> = callbackFlow{

    val document = fireStore.collection("database/user/userList/")
        .document("test!!!!!")

    val subscription = document.addSnapshotListener { snapshot,_ ->

        if (snapshot!!.exists()) {

            val ownUser = snapshot.toObject<UserEntity>()

            if (ownUser != null) {
                trySend(ownUser)
            }
        }
    }

    awaitClose { subscription.remove() }

}

so I really wonder these way is good or bad practice and its reason

1 Answers

Do not combine addOnCompleteListener with coroutines await(). There is no guarantee that the listener gets called before or after await(), so it is possible the code in the listener won't be called until after the whole suspend function returns. Also, one of the major reasons to use coroutines in the first place is to avoid using callbacks. So your first function should look like:

override suspend fun checkNickName(nickName: String): Results<Int> {
    try {
        val userList = fireStore.collection("database")
            .document("user")
            .get()
            .await()
            .get("nickNameList") as List<String>

        return if (userList.contains(nickName)) Results.Exist(1) else Results.No(0)
    } catch (e: Exception) {
        // return a failure result here
    }
}

Your use of callbackFlow looks fine, except you should add a buffer() call to the flow you're returning so you can specify how to handle backpressure. However, it's possible you will want to handle that downstream instead.

override fun getOwnUser(): Flow<UserEntity> = callbackFlow {
    //...
}.buffer(/* Customize backpressure behavior here */)
Related