Manideep Polireddi
04/15/2022, 2:07 PMAdam Powell
04/15/2022, 3:01 PMLaunchedEffect
and DisposableEffect
are implemented using the same underlying primitive: RememberObserver
. This might make an interesting addition to your samples:
remember {
object : RemembeObserver {
override fun onRemembered() {}
override fun onForgotten() {}
override fun onAbandoned() {}
}
}
DisposableEffect
and LaunchedEffect
comes from coroutine dispatch. LaunchedEffect
is doing a CoroutineScope.launch
in response to onRemembered
, and it doesn't start running immediately, it runs once the associated dispatcher schedules and runs it. On Android that means on the main thread after the current frame is done being processedJob
is `cancel()`ed in onForgotten
, just as onDispose
runs in onForgotten
Manideep Polireddi
04/15/2022, 3:09 PMAdam Powell
04/15/2022, 3:10 PMval obj = MyObject()
obj.init(someInitParams)
obj.property = someValue
obj.dispose()
then with compose you can drive that object like this:
val obj = remember { MyObject() }
DisposableEffect(obj)
obj.init(someInitParams)
onDispose {
obj.dispose()
}
}
SideEffect {
obj.property = someValue
}
val inner = remember { InnerObject() }
DisposableEffect(inner) {
inner.init()
onDispose { inner.dispose() }
}
val outer = remember(inner) { OuterObject(inner) }
DisposableEffect(outer) {
outer.init()
onDispose { outer.dispose() }
}
onForgotten
calls run in LIFO order, outer
will be disposed before `inner`; if outer.dispose()
requires inner
to still be valid - a fairly common setup - then everything Just Works and survives refactor/extract function on the code above. The ordering is based on order in the composition, not within a single composable function.Manideep Polireddi
04/15/2022, 3:16 PMAdam Powell
04/15/2022, 3:17 PMRememberObserver
is that its semantics can be kind of unexpected if you aren't familiar with how other parts of compose worksonRemember
when it is remembered in one or more places in a single composition, and onForgotten
when it was previously remembered in a composition but no longer appears anywhere within it.onRemembered
remember
calls. For example, compose internally remembers the parameters to composable function invocations - it has to, otherwise how would it be able to check new parameters vs. old to determine whether a composable can skip? This counts toward that refcount within a composition.RememberObserver
escape from where they're deliberately `remember {}`ed. Especially on Android, developers are used to optimizing object allocations by having objects do multiple duties, implementing several interfaces, but passing a reference to one of those interfaces elsewhere. When working with RememberObserver
that can have consequences.interface Thing {
fun doStuff()
}
private class ThingImpl : Thing, RememberObserver {
// ...
}
then as a developer, if I have a Thing
I don't know that it's going to have some potentially strange behavior if I ever pass it as an argument to a composable function.DisposableEffect
and LaunchedEffect
instead, since it's much harder to get into these cases when using those higher level APIsManideep Polireddi
04/15/2022, 3:32 PMAdam Powell
04/15/2022, 3:35 PMRememberObserver
stuff and save it for a later article, but the LIFO ordering for init/dispose is pretty important in terms of understanding why it works the way it doesManideep Polireddi
04/16/2022, 11:36 AMTrey
04/16/2022, 3:21 PM