https://kotlinlang.org logo
f

flosch

05/21/2020, 6:40 PM
Hey, I need a CoroutineScope that lives as long as the current composition. is this valid?
Copy code
@Composable
internal fun composeCoroutineScope(
    coroutineContext: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
): CoroutineScope {
    val scope = remember { CoroutineScope(coroutineContext) }
    onDispose { scope.cancel() }
    return scope
}
z

Zach Klippenstein (he/him) [MOD]

05/21/2020, 6:52 PM
Looks reasonable to me. I think there’s also a helper coming in dev12 for doing pretty much exactly this. Edit: See Adam’s message.
There is a
CoroutineContextAmbient
that you might want to incorporate, although I’m not sure if there’s any advantage to doing so since I don’t know what it would provide other than a parent job, which you don’t care about here anyway since you’re not bubbling errors up and cancelling it yourself.
a

Adam Powell

05/21/2020, 6:55 PM
Just getting a coroutine scope from a composable is kind of dangerous as the only safe way to launch something into it is from an onCommit or other event that can only occur after the composition successfully commits
You generally want something that looks like the API just merged above, where you launch with onCommit semantics and cancel that job if the launch call leaves the composition
Oops, didn't mean to send that to channel
z

Zach Klippenstein (he/him) [MOD]

05/21/2020, 7:01 PM
Ah, that’s what I was thinking of. Just out of curiosity, what could go wrong if you launch before the composition commits?
r

raulraja

05/21/2020, 7:07 PM
@Adam Powell would @RestricSuspension and making Composable
suspend
solve those issues?
a

Adam Powell

05/21/2020, 7:10 PM
in what way? Composable functions don't suspend and resume, they fully restart.
(Or skip, as the case may be)
@Zach Klippenstein (he/him) [MOD] Composition is transactional; it can happen speculatively and it can fail. By performing side effects only in onCommit-like constructs you can avoid having to detect these situations and attempt to roll back.
🤔 1
Today there aren't many situations where this happens, but we're in the process of setting things up so that composition can occur off the main thread. We'll be able to allow large recompositions that spend a lot of time in app code to span multiple frames, while the existing app UI can still continue animating by whatever mechanism, etc.
This will also allow all sorts of background prefetching of content in recyclerview-like scenarios
😮 4
r

raulraja

05/21/2020, 7:26 PM
RestrictSuspension restricts effects to a receiver which means you can selectively say which methods of a Composable would take state changing or potentially async code and which parts don’t. the std library provides the intrinsics to run suspension without dependencies in KotlinX Coroutines which would allow you run Composable functions with any semantics you want. You control how the continuation proceeds and what suspension points do. Also unlocks the feature you mention since everything mentioned above in terms of optimisations, executions or caching is just a particular runtime for a delimited continuation.
suspend has nothing to do with async, just an op that can fail or succeed and needs to be resumed blocking or async
f

flosch

05/21/2020, 7:27 PM
The
launchInCompose
looks cool 👍 but it’s not really what I need. Let’s say I have a presenter that is bound to the life of a
CoroutineScope
(and also created via
CoroutineScope.createPresenter
extension function) and I want to use that presenter for a Composable. Could I use the
effectCoroutineScope
?
r

raulraja

05/21/2020, 7:28 PM
otherwise in Kotlin how are you gonna be able to control effects that can go async and scape the main thread. At the moment Kotlin allows those anywhere unless you provide your own restricted suspended implementation
the kind of async power and optimizations over binding of views you mentioned as new features can be implemented because there is in the std lib startCoroutineUniterceptedOrReturn and COROUTINE_SUSPENDED
and those interoperate with all libs that have a suspend runtime like KotlinX or Arrow Fx
same reason why the sequence builder uses restricted suspension to yield without actually doing anything async
a

Adam Powell

05/21/2020, 7:32 PM
yep, I'm aware, I spent last weekend using exactly those primitives to do gesture detection with compose 🙂 it's quite nice
👍 1
r

raulraja

05/21/2020, 7:33 PM
so the question is why Composable functions are not suspended? what is the reason to make them regular functions?
a

Adam Powell

05/21/2020, 7:33 PM
they're not regular functions either 🙂
suspended continuations can only be resumed once, and we need to be able to restart from the beginning multiple times
r

raulraja

05/21/2020, 7:34 PM
suspended continuation can be resumed multiple times
a

Adam Powell

05/21/2020, 7:35 PM
so the resulting structure ends up different
r

raulraja

05/21/2020, 7:35 PM
just need to reset the stack labels if your structure is supposed to be immutable
Arrow Fx does this for all types, multi shot continuations for non deterministic monads
just need to add a new method in ContinuationImpl to reset the stack after resumption
easier than replicating suspend, currently requires a reflection hack
they can be rewinded and restrict suspension ensures you can perform the optiizations on each suspension point
like autocancelllation of jobs or fibers etc
or auto stack-safety for long tree composition by trampolining in suspension points
a

Adam Powell

05/21/2020, 7:41 PM
We evaluated it and it wasn't quite what we were looking for, and we spent some time with Roman and his team discussing the parallels between
suspend
and
@Composable
. There are certainly similarities, but ultimately we decided that they were different enough to keep separate
👍 1
which isn't to take away from how awesome
suspend
is 🙂
I find that the two complement each other extremely well and form a sort of feedback loop between each other
@flosch Can you post an example of the code that uses such a presenter from compose?
f

flosch

05/21/2020, 7:51 PM
Sure
Copy code
@Composable
private fun AppScreen() {
    MaterialTheme(colors = AppColors.currentColorPalette) {
        val controller = /*coroutineScope*/.createCounterController()
        val controllerState by controller.state.collectAsState()
        CounterScreen(counterState = controllerState, action = controller::dispatch)
    }
}
a

Adam Powell

05/21/2020, 7:55 PM
How about the presenter code itself? I tend to prefer keeping objects from owning `CoroutineScope`s and instead preferring exposing
suspend
functions as their API surface, which ensures the caller of those functions is what brings a relevant scope along
this often leads to having
foo.runBar()
suspend functions that get launched from elsewhere to act as consumers or workers, which can seem a bit cumbersome until you realize that you can `try`/`catch` around those and have some very nice error reporting and recovery, rather than dealing with `SupervisorJob`s and `CoroutineExceptionHandler`s that have to put things together after the fact
f

flosch

05/21/2020, 7:57 PM
Well the Controller approach is somewhat of a MVI situation where the Controller lives in by itself (as long as the CoroutineScope) and collectors dispatch actions and receive a state as Flow. Now this was not particularly made for Compose and I am trying to get this to run smoothly with Compose 🙂
a

Adam Powell

05/21/2020, 8:00 PM
Strictly speaking what you're doing will mostly work, though you're over-`remember`ing the scope. You might want to pass the
coroutineContext
supplied as a parameter to
remember
as a key param, so that if the caller changes the context for some reason, you can cancel the old scope and get a new one. That makes the
onDispose
dance you're doing a bit different though
For app code I probably wouldn't worry about it, but it's the sort of thing I would want to be very precise about if I were writing a library for wider consumption
If an object implements
CompositionLifecycleObserver
it will get callbacks when it enters or leaves a composition. In other words, if you
remember
an object that implements that interface, compose will call its
onEnter
and
onLeave
methods automatically for you, and you won't need a separate
onDispose
f

flosch

05/21/2020, 8:16 PM
For app code I probably wouldn’t worry about it, but it’s the sort of thing I would want to be very precise about if I were writing a library for wider consumption
Yes, I am currently trying to figure out if using my lib with compose is possible out of the box or if I should provide a way to create a Controller for `@Composable`s without a specific
CoroutineScope
Thanks for your help though! 🙂
c

codeslubber

05/25/2020, 8:49 PM
Much as I see utility in coroutines, I still would say if someone could really solve composability and concurrency (scoping), they should be entitled to a Genius award. (We should have them in tech: fix this intractable problem and we’ll give you a huge plaque and a million bucks…)