kobowo
kobowo

Reputation: 2787

Unable to Execute code after Kotlin Flow collect

I'm trying to execute some code after calling collect on a Flow<MyClass>. I'm still kind of new to using Flows so I don't understand why the code after the function doesn't get called.

How I use the Flow:

incidentListener = FirebaseUtils.databaseReference
                      .child(AppConstants.FIREBASE_PATH_AS)
                      .child(id)
                      .listen<MyClass>() //This returns a Flow<MyClass?>?

How I consume the Flow:

private suspend fun myFun() {
   viewmodel.getListener()?.collect { myClass->
       //do something here
   }
   withContext(Dispatchers.Main) { updateUI() } //the code never reaches this part
}

How myFun() is called:

CoroutineScope(Dispatchers.IO).launch {
   myFun()
}

As far as what I've tried to make it work I've tried closing the coroutine context and it didn't work. I'm assuming Flows work differently than regular coroutines.

Update:

I'm listening through Firebase using this block of code. I don't know if it'll help but maybe the way I implemented it is causing the issue?

inline fun <reified T> Query.listen(): Flow<T?>? =
callbackFlow {
    val valueListener = object : ValueEventListener {
        override fun onCancelled(databaseError: DatabaseError) {
            close()
        }

        override fun onDataChange(dataSnapshot: DataSnapshot) {
            try {
                val value = dataSnapshot.getValue(T::class.java)
                offer(value)
            } catch (exp: Exception) {
                if (!isClosedForSend) offer(null)
            }
        }
    }
    addValueEventListener(valueListener)
    awaitClose { removeEventListener(valueListener) }
}

Upvotes: 23

Views: 12725

Answers (2)

kobowo
kobowo

Reputation: 2787

I forgot to post my own answer to this. I've found the problem before. It's because I wasn't returning the Coroutine Context.

My code has been updated since but with the code above as an example it should be written as follows:

private suspend fun myFun() {
   viewmodel.getListener()?.collect { myClass->
       //do something here
       return@collect
   }
   withContext(Dispatchers.Main) { return@withContext updateUI() } 
   //the code should flow downwards as usual
}

Upvotes: 6

Francesc
Francesc

Reputation: 29330

collect is a suspending function, the code after collect will only run once the flow completes.

Launch it in a separate coroutine:

private suspend fun myFun() {
   coroutineScope {
       launch {
           viewmodel.getListener()?.collect { myClass->
               //do something here
           }
       }
       withContext(Dispatchers.Main) { updateUI() } //the code never reaches this part
    }
}

Upvotes: 21

Related Questions