https://kotlinlang.org logo
Title
r

rook

05/30/2019, 7:43 PM
Is it possible to use channels to wait for a signal to do work? Something like:
suspend fun waitForUpdates(): ReceiveChannel<Update> = produce {
  onUpdateHook { update ->
    send(update)
  }
}

fun startWaitingForUpdates() {
  launch {
    waitForUpdates().onReceive { update ->
      doUpdate(update)
    }
  }
}
s

streetsofboston

05/30/2019, 7:53 PM
Absolutely. Be sure that the `produce`’s lambda does not return to keep the channel open. (see the
suspendCoroutine
call in
waitForUpdates
.
fun CoroutineScope.waitForUpdates(): ReceiveChannel<Update> = produce {
    onUpdateHook { update ->
        send(update)
        suspendCoroutine<Nothing> { /* do nothing */ }
    }
}

fun CoroutineScope.startWaitingForUpdates() {
    launch {
        waitForUpdates().apply {
            select {
                onReceive { update ->
                    doUpdate(update)
                }
            }
        }
    }
}
r

rook

05/30/2019, 7:59 PM
suspendCoroutine<Nothing> { /* do nothing */ }
^ this right here was the missing link, thank you!
s

streetsofboston

05/30/2019, 8:03 PM
If you need to be able to close the channel on a failure/update-close signal, use something like this: E.g.:
fun CoroutineScope.waitForUpdates(): ReceiveChannel<Update> = produce {
    suspendCoroutine<Unit> { cont ->
        onUpdateHook { update ->
            send(update)
        }
        onUpdateTermination { error ->
            if (error != null) {
                cont.resumeWithException(error)
            }
            else {
                cont.resume(Unit)
            }
        }
    }
}
r

rook

05/30/2019, 8:07 PM
Great! Thanks so much!
d

Dico

05/30/2019, 9:45 PM
send
won't work in some of these contexts, use
offer
with a conflated channel.
r

rook

05/30/2019, 9:54 PM
yeah, I was just realizing that I lose my coroutine body in the context of the lambda
s

streetsofboston

05/30/2019, 9:57 PM
It would depend on how the signature of
onUpdateHook
looks like. You can make its lambda-parameter
suspend
(and make it an extension-function of CoroutineScope) if synchronization is important. Otherwise, just call
offer
like Dico said 🙂
r

rook

05/30/2019, 10:13 PM
Unfortunately, I don’t have control over
onUpdateHook
. Thanks for all the help!
A quick follow-up question, is there a way to use something like
produce
without a
CoroutineScope
? I’m trying to get my repository interface to return a
channel
, but if I declare
waitForUpdates()
as an extension of
CoroutineScope
, I can’t call the function unless it’s from within the context of the repository object
b

bdawg.io

05/30/2019, 11:03 PM
You could manage the lifecycle of the channel yourself. Channels can be created using the
Channel()
function
produce
and
actor
are implementations that ensure that the Channel's lifecycle is tied to the lifecycle of your coroutine so it doesn't leak
r

rook

05/30/2019, 11:07 PM
How would I go about tying my channel’s lifecycle to the lifecycle of a coroutine in which it’s containing
suspend
function is invoked?
s

streetsofboston

05/30/2019, 11:38 PM
suspend fun waitForUpdates(): ReceiveChannel<Update> = coroutineScope { ... }
. The lambda of the
coroutineScope
has a
this
receiver that is a
CoroutineScope
instance.
💯 1