I don’t understand this explanation in the docs: _...
# compose
m
I don’t understand this explanation in the docs: > `rememberUpdatedState`: reference a value in an effect that shouldn't restart if the value changes >
LaunchedEffect
restarts when one of the key parameters changes. However, in some situations you might want to capture a value in your effect that, if it changes, you do not want the effect to restart. In order to do this, it is required to use
rememberUpdatedState
to create a reference to this value which can be captured and updated. This approach is helpful for effects that contain long-lived operations that may be expensive or prohibitive to recreate and restart. > For example, suppose your app has a
LandingScreen
that disappears after some time. Even if
LandingScreen
is recomposed, the effect that waits for some time and notifies that the time passed shouldn't be restarted: >
Copy code
@Composable
> fun LandingScreen(onTimeout: () -> Unit) {
> 
>     // This will always refer to the latest onTimeout function that
>     // LandingScreen was recomposed with
>     val currentOnTimeout by rememberUpdatedState(onTimeout)
> 
>     // Create an effect that matches the lifecycle of LandingScreen.
>     // If LandingScreen recomposes, the delay shouldn't start again.
>     LaunchedEffect(true) {
>         delay(SplashWaitTimeMillis)
>         currentOnTimeout()
>     }
> 
>     /* Landing screen content */
> }
> use
rememberUpdatedState
to create a reference to this value which can be captured and updated This left me under the impression that it’ll remember the first value it was composed with throughout the rest of the lifetime of the
@Composable
instance. > This will always refer to the latest onTimeout function Which this seems to contradict. > To make sure that the
onTimeout
lambda always contains the latest value that
LandingScreen
was recomposed with,
onTimeout
needs to be wrapped with the
rememberUpdatedState
function Isn’t the whole point of
@Composable
functions that they run again when a state they depend on has changed? Every parameter of a composable sees the latest argument the composable was called with, does it not? So why do I suddenly need
rememberUpdatedState
?
s
It says "captured and updated" which part of this makes you think it will be the same for the rest of the lifetime?
LaunchedEffect would have to have the onTimeout as a key so that if it changed it'd know about it. But doing so would restart the effect and then also restart the delay function there. That's why this technique is used here. Composables do run again when their input has changed as you say, yes. But a LaunchedEffect only reruns when the keys passed to it changes.
m
which part of this makes you think it will be the same for the rest of the lifetime?
@Stylianos Gakis I didn’t really understand what the docs meant by ‘updated’, because 1. that’s not how remembering usually works, and 2. so far I’ve not had to do anything to provide
@Composable
functions with ‘updated’ arguments aside from calling it again with updated values.
But doing so would restart the effect and then also restart the delay function there. That's why this technique is used here.
So it’s like remembering and capturing a cell to update?
Copy code
val currentOnTimeOut by remember {
    object { var current = onTimeOut }
}
currentOnTimeOut.current = onTimeOut

LaunchedEffect(true) {
    // ...
    currentOnTimeOut.current()
}
So the object is persisted throughout the rest of the
@Composable
node lifetime and shared between the body and the
LaunchedEffect
?
s
Kinda like that yes, but not the exactly same because you're remembering an object which holds a var which is not backed by mutable state. Look inside the implementation of rememberUpdatedState and you'll see how the two differ. Having it be mutableState also means that it then works with if you are reading that updated state in composition or if you pass it down to some other composable
m
Oh it’s literally just
Copy code
{ arg ->
    val state = remember { mutableStateOf(/* initial */ arg) }
    state.value = /* latest */ arg
    state
}
I see, thanks
s
Yeap that's the entire implementation 😅
e
My advice is to always read the code tbh
💯 1
c
The reason
rememberUpdatedState()
exists at all it is a pattern that you can subtly get wrong. The things it does is important. 1) It remembers the
mutableStateOf()
2) the state object it returns is always the same state object, 3) the state is always updated with parameter value passed in. These appear obvious but we saw wrong versions of this often enough to create a helper.
🙏 1