question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

wrapping callbacks that fire more than once

See original GitHub issue

I am now trying to convert some Firebase code on Android to use coroutines. some Firebase callbacks are a bit weird in that they sometimes trigger more than once. For example for getting the download url from firebase storage I have written this:

    private suspend fun getFileDownloadUrlAsync(file_sref: StorageReference): Uri = suspendCoroutine { c ->
        with(file_sref.downloadUrl) { //<---- call to Firebase getDownloadUrl()
            addOnCompleteListener { //<--- this callback can trigger more than once!
                if (it.isSuccessful) {
                    c.resume(it.result)
                } else
                    c.resumeWithException(Exception("some error"))
            }
            addOnFailureListener {
                c.resumeWithException(it)
            }
        }
    }

Since the completion listener is triggered more than once I get an IllegalStateException(“Already resumed”) As a work-around I have defined

    class WrappedContinuation<T>(val c: Continuation<T>) : Continuation<T> {
        var isResolved = false
        override val context: CoroutineContext
            get() = c.context

        override fun resume(value: T) {
            if (!isResolved) {
                isResolved = true
                c.resume(value)
            }
        }

        override fun resumeWithException(exception: Throwable) {
            if (!isResolved) {
                isResolved = true
                c.resumeWithException(exception)
            }
        }

    }

    public inline suspend fun <T> suspendCoroutineW(crossinline block: (WrappedContinuation<T>) -> Unit): T =
            suspendCoroutine { c ->
                val wd = WrappedContinuation(c)
                block(wd)
            }

and I am using suspendCoroutineW instead of suspendCoroutine. Would it be possible to modify SafeContinuation class in the library to offer similar functionality, since it already keeps the information about if the continuation has already been resolved or not, and expose it to the user?

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:3
  • Comments:8 (3 by maintainers)

github_iconTop GitHub Comments

6reactions
beyondeyecommented, Apr 4, 2017

ok so I find the solution: I should have used suspendCancellableCoroutine

4reactions
beyondeyecommented, Apr 6, 2017

suspendCancellableCoroutine build a SuspendableCoroutine that exposes (among other stuff) the two methods

public fun tryResume(value: T, idempotent: Any? = null): Any?
public fun tryResumeWithException(exception: Throwable): Any?

that basically do what I want: they check the continuation state (that I cannot access directly because it is internal data), if it was already resumed. About Firebase: for that specific API (getFileDownloadUrl) the callback is called twice almost always. There are also other Firebase APIs that behave the same way. I don’t know the cause. It is a documented behavior, but I never actually understood why this should happen at all. But this is how it works.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Preventing callbacks from accidentally being called twice
Preventing callbacks from accidentally being called twice · Use an else branch even if it's not necessary · Wrap the callback so calling...
Read more >
Callback Gotchas | Dash for Python Documentation | Plotly
In this situation, where a component registered with a callback is missing from the layout, the callback will fail to fire. For example,...
Read more >
Exploring RxJava - Wrapping a Listener Callback - Couchbase
A brief tutorial showing how to take a callback-based listener style API (think button clicks) to a reactive one using Observables with ...
Read more >
Existing 3-function callback to Kotlin Coroutines - Stack Overflow
Chaining callbacks works if there ale multiple callbacks in sequence, each providing a result or error. <s>Here are two callbacks in parallel, ...
Read more >
Common mistakes with React Testing Library - Kent C. Dodds
Because of this, the callback can be called (or checked for errors) a non-deterministic number of times and frequency (it's called both on...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found