0

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.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
marsuser
  • 125
  • 9
  • Do you know exactly how many elements should exist in the list when all files are added? is it a fixed number? – MehranB Feb 18 '22 at 09:51

3 Answers3

2

You can achieve it using this way by utilizing the Task. Tasks.whenall() will wait until all task are done.

fun functionA() {

    val taskList = mutableListOf<Task<DataSnapshot>>()
    val resultFileDataList = List<DataSnapshot>()

    for ((key, value) in filesMap) {
        val databaseReferenceTask: Task<DataSnapshot> = database.child("files").child(key).get()
        taskList.add(databaseReferenceTask)

        val resultTask = Tasks.whenAll(taskList)
        resultTask.addOnCompleteListener {
            for (task in taskList) {
                val snapshotKey: String? = task.result.key
                val snapShotValue = task.result
            }
            
            callFunctionB() 
        }
    }
}
Praneeth
  • 627
  • 8
  • 11
0

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.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • I am exactly looking for pipelining requests but the example link you shared is for iOS. Could you share similar code for android? I tried searching for it but couldn't find anything for android. – marsuser Feb 18 '22 at 21:02
  • I found a useful answer [here](https://stackoverflow.com/questions/70436260/how-to-optimize-real-time-database-calls-when-getting-10-random-users). Give it a try and tell me if it works. – Alex Mamo Feb 19 '22 at 09:39
0

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.

MehranB
  • 1,460
  • 2
  • 7
  • 17
  • Thanks for adding this. I do not want to depend on the size of list and that is not fixed and can be changed in between as well. I am looking for similar code in android that awaits for all responses. [Pipelining requests](https://stackoverflow.com/questions/35931526/speed-up-fetching-posts-for-my-social-network-app-by-using-query-instead-of-obse/35932786#35932786) – marsuser Feb 18 '22 at 21:04