Franco
04/12/2020, 9:05 AMsuspendCancellableCoroutine completes so I can unsubscribe from the callback that might return more values later on.
My use case is that I'm trying to get the user from Firebase using the onAuthStateChanged() listener as they suggest here. However, I don't want to keep listening for this after getting it at the start of the app and if I don't unsubscribe from it then I get the error IllegalStateException: Already resumed, but proposed with update .
This is an example of my code:
suspend fun getUser(): User = suspendCancellableCoroutine { continuation ->
val unsubscribe = firebaseAuth.onAuthStateChanged(
nextOrObserver = { user -> continuation.resume(user) },
error = { error -> continuation.resumeWithException(...) }
)
}Erik
04/12/2020, 9:59 AMonAuthStateChanged function returns an unsubscribe function. When you get the first callback, invoke the unsubscribe function before passing the user to resume the continuation.Franco
04/12/2020, 10:04 AMunsubscribe function can't be found inside the callback itself.
Do you have any idea if I might be doing something wrong? The code is the same I shared plus adding the unsubscribe() call inside the callback and the error I see is Unresolved reference: unsubscribeErik
04/12/2020, 10:06 AMval unsubscribe in your example. Let me think of somethingFranco
04/12/2020, 10:08 AMErik
04/12/2020, 10:13 AMsuspendCancellableCoroutine { continuation ->
lateinit var unsubscribe: firebase.Unsubscribe /* or whatever type it is */
val onUser = { user: User ->
unsubscribe()
continuation.resume(user)
}
unsubscribe = firebaseAuth.onAuthStateChanged(
nextOrObserver = onUser,
error = { error -> continuation.resumeWithException(...) }
)
}Erik
04/12/2020, 10:13 AMErik
04/12/2020, 10:13 AMlateinit var you could also use a nullable var, which also isn't pretty ๐Erik
04/12/2020, 10:15 AMcontinuation.invokeOnCancellation { }Franco
04/12/2020, 10:51 AMonComplete() on the continuation ๐
I had tried something similar which had not worked, I guess the trick is to take the callback outside of the onAuthStateChanged call.fatih
04/12/2020, 10:54 AMFranco
04/12/2020, 11:07 AMcompleted from the onAuthStateChanged @fatih, the one I'd like to have is the one from the coroutine continuationFranco
04/12/2020, 11:09 AMonAuthStateChanged but it doesn't seem to be called after a value is received, I tested it just nowErik
04/12/2020, 11:17 AMresume on it, i.e. when the suspend fun returns a value (or throws)Franco
04/12/2020, 12:07 PMonComplete callback so I can call the unsubscribe from without having to add the hacky codeDominaezzz
04/12/2020, 1:11 PMsuspend fun getUser(): User {
return callbackFlow<User> {
val unsubscribe = firebaseAuth.onAuthStateChanged(
nextOrObserver = { user -> channel.offer(user) },
error = { error -> channel.close(error) }
)
awaitClose { unsubscribe() }
}.first()
}Franco
04/12/2020, 2:17 PMDominaezzz
04/12/2020, 2:24 PMsuspend fun getUser(): User {
val latch = CompletableDeferred<User>()
val unsubscribe = firebaseAuth.onAuthStateChanged(
nextOrObserver = { user -> latch.complete(user) },
error = { error -> latch.completeExceptionally(error) }
)
try {
return latch.await()
} finally {
unsubscribe()
}
}Erik
04/12/2020, 3:00 PMonAuthStateChanged and unsubscribe must be decoupled through some communication mechanism, e.g. a channel or the completable deferred. I agree that a flow better represents this Firebase API.Franco
04/12/2020, 5:05 PM