https://kotlinlang.org logo
#coroutines
Title
# coroutines
a

Ale

09/07/2020, 11:29 AM
I'm using callbackFlow to adapt a callback from Firestore into a flow, and then store the received data locally, as a "fire and forget" call. I can do this:
Copy code
@ExperimentalCoroutinesApi
private fun retrieveFromFirestore(porteroId: String): Flow<Portero> = callbackFlow {
    val porteroCallback = EventListener<QuerySnapshot> { querySnapshot, e ->
        val element = //extract data from snapshot etc.
             launch { //save to local sqllite }
        offer(element)
    }
}
This works well. However, when I tried to refactor the code a bit, and extract the snapshot manipulation to another method:
Copy code
@ExperimentalCoroutinesApi
private fun retrieveFromFirestore(porteroId: String): Flow<Portero> = callbackFlow {
    val porteroCallback = EventListener<QuerySnapshot> { querySnapshot, e ->
        val element = buildData(querySnapshot, e)
        offer(element)
    }
}

private buildData(querySnapshot: QuerySnapshot?, e: FirebaseFirestoreException?): Element {
    //extract data from snapshot etc.
    launch { //save to local sqllite }  // <--- this doesn't compiles
    return element
}
The launch function is no longer available, with the error Unresolved reference. I can fix it using
CoroutineScope(Dispatchers.Default).launch { ... }
but why is this happening? How come launch is available only in the calling function but not in the called one?
m

Manuel Vivo

09/07/2020, 11:34 AM
You have to make it a
suspend
function, and in order to create new coroutines inside a suspend function, you need a scope (e.g.
coroutineScope
or
supervisorScope
depending on whether you want a
Job
or
SupervisorJob
as a parent)
w

wasyl

09/07/2020, 11:38 AM
And more specifically to answer your question why
launch
is not accessible anymore:
launch
is an extension function on
CoroutineScope
. The `callbackFlow`’s parameter (the lambda) is defined as
block: suspend ProducerScope<T>.() -> Unit
, and
ProducerScope
implements `CoroutineScope`:
interface ProducerScope<in E> : CoroutineScope
. So when calling
launch
inside
callbackFlow
lambda you simply have a
CoroutineScope
in scope. When you extract a function like this, you need to follow what Manuel said: make the extracted function `suspend`able and start a new scope using
coroutineScope { }
builder.
a

Ale

09/07/2020, 11:42 AM
Ah, I see, the key part is that callbackFlow is a suspended block! Now it makes sense, thank you!
9 Views