Why is it that whenever calling a callback passed ...
# compose-android
d
Why is it that whenever calling a callback passed to a composable inside, say, onClick of a button, I never saw anyone using rememberUpdatedState, whereas when calling it from a LaunchedEffect, it seems to be needed? And when would such a callback be changed? It's usually something like
navController.navigate(...)
that usually stays the same for that composable...
v
The
.clickable
modifier updates the saved lambda on every recomposition if it has changed.
LaunchedEffect
does not restart if its keys do not change.
You can check
AbstractClickableNode
and it's
updateCommon
method
d
But why would the lambda ever change?
If in the call site there would be an
if
to decide what to do based on something outside of it, maybe I'd understand... but if not, then we still need to use rememberUpdatedState?
v
who knows, depends on what you want to implement 😄 it's not restricted to lambdas but any arbitrary values that might change
y
Isn't any lambda a really cheap objection allocation on each run through? I forget if it's more efficient with references
v
the point isn't really in allocating the lambda, it's that the
LaunchedEffect
lambda holds a reference to the old value in its closure even if it changes
consider this:
Copy code
@Composable fun Foo(activate: () -> Unit) {
  LaunchedEffect(Unit) {
    delay(5000)
    activate()
  }
}
vs
Copy code
@Composable fun Foo(activate: () -> Unit) {
  val updatedActivate = rememberUpdatedState(activate)
  LaunchedEffect(Unit) {
    delay(5000)
    updatedActivate.value()
  }
}
this
Foo
cannot know in advance if it will be ever used by a parent that will recompose it with a different
activate
before it calls
activate()
d
How could that happen on the one calling Foo?
Copy code
@Composable fun Bar(nav: NavController) { Foo { nav.navigate("some-destination") } }
would never change... what would be considered that could change?
Copy code
@Composable fun Bar(nav: NavController, dest: String) { Foo { nav.navigate(dest) } }
?
Then the LaunchedEffect would be cancelled already no?
v
well in that case it wouldn't change unless you create a new navcontroller
but consider the case where you want to navigate to a different location based on some other condition
d
When LaunchedEffect enters the composition it will launch block into the composition's CoroutineContext. The coroutine will be cancelled and re-launched when LaunchedEffect is recomposed with a different key1. The coroutine will be cancelled when the LaunchedEffect leaves the composition.
Unless leaving the composition means that Foo isn't called on recomposition?
v
Copy code
@Composable fun Qux(isLoggedIn: Boolean) {
    Bar(navController, if (isLoggedIn) "target1" else "target2")
}
qux could be called while logged in, but then the user logs out or is logged out by some external event before the timeout completes
the first variant of Foo would navigate to target1, while the second variant would navigate to target2
you could of course, for example, add the key to the launchedEffect, but then the timeout would also be ran again
👍🏼 1
Copy code
LaunchedEffect(activate) { ... }
d
I see... so we're just protecting ourselves from such cases... but when static, it's superfluous. And so when is it that it "leaves the composition" that the LaunchedEffect gets cancelled?
v
(of course this is just a hypothetical example, but I'm just trying to demonstrate a case where the difference does matter)
as a general rule, you would only need
rememberUpdatedState
with LaunchedEffect when you have a long running operation in LaunchedEffect that you also don't want to restart for some reason
👍🏼 1
d
you also don't want to restart for some reason
By using `LaunchedEffect`'s parameter to restart on that lambda change instead? But otherwise, it would only get cancelled when
Foo
doesn't get called on a future recomposition and the task is still running?
v
yes, exactly
if you don't specify any keys, or the key never changes, it will get cancelled only when it's removed from the composition
d
Wow, thanks for the explanation 😄! It's the kind of thing that's hard to grasp at first...
v
yeah, it takes some time to adjust your mindset to declarative UI
glad I could help