Is there any difference between `LaunchedEffect` a...
# compose
l
Is there any difference between
LaunchedEffect
and
DisposableEffect
when you don't actually need any cleanup? I've seen in the official samples some `DisposableEffect`s with an empty
onDispose {}
, and that makes me think
LaunchedEffect
should be a better option, but I'm in doubt 🤔
a
If you need to publish changes from composition to external objects you can use
SideEffect
and if all three are feeling awkward for your use case that might be on purpose. 🙂 Can you post a bit more about the specific use case?
l
I'm not looking for some particular use case at the moment. I was looking at the implementation and samples of
Focusable
the other day and saw a few of these: https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/[…]in/kotlin/androidx/compose/foundation/Focusable.kt;l=79;bpv=1 https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/[…]Demo&ss=androidx%2Fplatform%2Fframeworks%2Fsupport:compose%2F And I was wondering why it's using a
DisposableEffect
if there's nothing to dispose of rather than a simpler
LaunchedEffect
. My guess is that it doesn't matter (they're doing the same), but maybe I'm missing something, and that's why I asked simple smile
a
The first one should probably be written differently, the second one is a pretty rare case that comes up occasionally and it's generally fine so long as you're careful about where and why you're using it
There are some trivial differences in overhead and one not so trivial difference in behavior between DisposableEffect and LaunchedEffect; LaunchedEffect has slightly more overhead from the coroutines machinery but if you're noticing a difference from that then you're using this pattern way too much. LaunchedEffect also won't start running until the next frame due to coroutine dispatcher behavior, but DisposableEffect will run its setup code after composition apply but before measure/layout/draw happens for the current frame. Sometimes this can affect correctness or jank one way or the other but only for fairly rare cases.
👍🏼 1
👍 6
If you only ever use effects to publish results of composition and not to try to affect the current composition, you'll never notice a correctness issue from this
If an effect can synchronously alter inputs of both composition and layout/drawing then you can see a shear where for that frame, layout/draw will see new data but the composition will still reflect old data
l
Oh, I see! I knew
LaunchedEffect
was delayed by a frame, but I didn't know
DisposableEffect
isn't delayed. Maybe it's worth mentioning this in the
Effects.kt
file? My initial thinking was that they're the same but one allows to dispose of something if the key changes or it leaves the composition
I have a
LaunchedEffect
on one of my screens of a
BottomNavigation
which receives a function to allow to hide the bottom navigation based on a condition (when selection mode is enabled on the screen or a bottom sheet is shown:
Copy code
LaunchedEffect(vm.selectionMode, sheetState.targetValue) {
  requestHideBottomNav(vm.selectionMode || sheetState.targetValue != ModalBottomSheetValue.Hidden)
}
In this case, maybe it's worth changing this to a
DisposableEffect
in case for some reason the app is started in selection mode, so that hiding the bottom nav isn't delayed by a frame 🤔
Or just remove the launched effect and call that function on every recomposition (it only changes the value of a
MutableState
)
a
sounds like a job for
SideEffect
(which also runs during the apply step rather than on the next frame)
l
Just tried that, but
SideEffect
does not call its effect when those mutable states are changed 🤔
I feel like I shouldn't be using any Effect for this situation. The function is only updating a
MutableState
that is managed by the parent (the one that contains the Scaffold and BottomNav)
a
this looks like it's trying to resolve some underlying source of truth issue by syncing between your ViewModel and other remembered state objects
l
Above is the child view, and the parent has this (the second parameter is passed down to the child):
Copy code
val (requestedHideBottomNav, requestHideBottomNav) = remember { mutableStateOf(false) }
And the bottom nav uses this logic to show/hide
val isVisible = TopLevelRoutes.isTopLevelRoute(currentRoute) && !requestedHideBottomNav
Yep, I'm not too convinced with my approach (at first I was thinking about an implementation similar to
BackHandler
where the inner-most call takes precedence/control) but I found this easier to implement. I just wanted to give a child screen/composable the ability to control the visibility of the bottom nav, rather than leaking that logic into the parent (as if it were a God activity) so the parent doesn't need to know the details of the child implementation