Reputation: 145
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.
Upvotes: 0
Views: 1487
Reputation: 637
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()
}
}
}
Upvotes: 3
Reputation: 1525
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)
}
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.
Upvotes: 0
Reputation: 138824
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.
Upvotes: 0