Home > Enterprise >  Firebase Realtime Database wait until the data is retrieved
Firebase Realtime Database wait until the data is retrieved

Time:02-19

I have a map containing the keys of my Firebase Realtime Database and want to retrieve the corresponding key data and put it in the result data list. How can I execute the loop sequentially? Basically, block the Firebase listener until it gets the result and only then iterate to the next key in the loop.

fun functionA() {

     val resultFileDataList = List<DataSnapshot>()
     for ((key, value) in filesMap) {
           val dbRef = database.child("files").child(key)
           dbRef.addListenerForSingleValueEvent(object : ValueEventListener {
                 override fun onCancelled(p0: DatabaseError) {}
                 override fun onDataChange(dataSnapshot: DataSnapshot) {
                        resultFileDataList.add(dataSnapshot)
                      }
                })
      }

    callFunctionB() // call this function only after all the data in the loop above is retrieved
}

I tried runBlocking {} but no luck.

CodePudding user response:

Since you are using Kotlin, then the simplest solution would be to use Kotlin Coroutines. In this way, you can use suspend functions and call await for each read operation. To achieve that, please check the following article:

If you need however to pipeline the requests over its existing connection, then you should consider using kotlinx-coroutines-play-services, case in which you can use awaitAll() function.

CodePudding user response:

This is one way to do it:

suspend fun functionA() = suspendCoroutine<List<DataSnapshot>>{ continuation ->

    val resultFileDataList = mutableListOf<DataSnapshot>()
    for ((key, value) in filesMap) {
        val dbRef = database.child("files").child(key)
        dbRef.addListenerForSingleValueEvent(object : ValueEventListener {
            override fun onCancelled(p0: DatabaseError) {}
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                resultFileDataList.add(dataSnapshot)
                if(resultFileDataList.size == fileMaps.size){
                    continuation.resume(resultFileDataList)
                }
            }
        })
    }
}

And then you can call the functions wherever you want like so:

CoroutineScope(Dispatchers.IO).launch {
    val dataSnapshotList = functionA()
    functionB(dataSnapshotList)
}

Bear in mind that it is better to use the following to bind the coroutine to the lifecycle of the activity:

lifecycleScope.launch(Dispatchers.IO) {
    val dataSnapshotList = functionA()
    functionB(dataSnapshotList)
}

Note:

This will basically wait for all the data to change so that the onDataChanged() is triggered and when the last file is added, continues with the coroutine and returns the value. Depending on your user's behaviour, this could take a long time to complete since even if one of the files is not changed, the coroutine will not resume.

Also, if onCancelled() is triggered for one file, this will never complete. So if you are absolutely sure that onDataChanged() will be triggered for all files, use this. Otherwise, implement some sort of timeout functionality to resume with the incomplete data.

  • Related