Reputation: 4231
I'm confused, can someone explain me the difference between:
val variable by remember { mutableStateOf() }
and
val variable by rememberUpdatedState()
When I check the source code of rememberUpdatedStates
I actually see: remember { mutableStateOf() }
@Composable
fun <T> rememberUpdatedState(newValue: T): State<T> = remember {
mutableStateOf(newValue)
}.apply { value = newValue }
Upvotes: 33
Views: 15936
Reputation: 10450
I want to elaborate more on rememberUpdatedState
. What it does is it
LaunchedEffect
key
.I created a demontration below to illustrate its benefits.
We have a LoggingScreen
Composable that does some work and then executes a certain function afterwards that we passed as a parameter. In the MainScreen
Composable, we change the function that is passed to the LoggingScreen
by clicking a Button
:
@Composable
fun MainScreen() {
val logAAA: () -> Unit = { Log.d("LOG", "AAA") }
var logFunction by remember {
mutableStateOf(logAAA)
}
Column {
Button(
onClick = {
logFunction = { Log.d("LOG", "BBB") }
Log.d("LOG", "changed logFunction to BBB...")
}
) {
Text(text = "CHANGE LOG OUTPUT")
}
LoggingScreen(logFunction)
}
}
@Composable
fun LoggingScreen(
logStatement: () -> Unit
) {
LaunchedEffect(Unit){
Log.d("LOG", "starting LaunchedEffect...")
delay(8_000) // do some work
logStatement()
}
}
When we start the App and click the Button
within 5 seconds, the log output printed still is
starting LaunchedEffect...
changed logFunction to BBB...
AAA
even though we would expect BBB
. This happens because AAA
was the value passed to the LaunchedEffect
when the Composable was started initially.
key
We usually fix this problem by specifying the logFunction
as key
of the LaunchedEffect
. That means that the LaunchedEffect
will be executed initially plus every time the logFunction
value changes.
@Composable
fun LoggingScreen(
logStatement: () -> Unit
) {
LaunchedEffect(logStatement){
Log.d("LOG", "starting LaunchedEffect...")
delay(5_000)
logStatement()
}
}
When we run this and click the Button
within 5 seconds, we see the following logs:
starting LaunchedEffect...
changed logFunction to BBB...
starting LaunchedEffect...
BBB
We see that the LaunchedEffect
is restarted once the logFunction
changes. This makes sure that the correct value BBB
is being logged. However, all work previously done in the LaunchedEffect
like the delay
is now restarted.
This might not be what we want, especially when we are doing time-consuming tasks. And for this exact scenario, rememberUpdatedState
offers the solution.
rememberUpdatedState
@Composable
fun LoggingScreen(
logStatement: () -> Unit
) {
val currentLogStatement by rememberUpdatedState(newValue = logStatement)
LaunchedEffect(Unit){
Log.d("LOG", "starting LaunchedEffect...")
delay(8_000)
currentLogStatement()
}
}
We are getting the following log output now:
starting LaunchedEffect...
changed logFunction to BBB...
BBB
Finally, the LaunchedEffect
holds the latest value of the logFunction
without needing to restart it.
After inspecting that example, the documentation becomes pretty clear:
LaunchedEffect
restarts when one of thekey
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 userememberUpdatedState
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.
Upvotes: 3
Reputation: 66674
remember
is needed when you don't want to do some heavy calculation/operation when your composable is recomposed. On the other hand, sometimes your operation might change so you need to do calculations or update remembered values to make sure not to use obsolete values from the initial calculation.
fun <T> rememberUpdatedState(newValue: T): State<T> = remember {
mutableStateOf(newValue)
}.apply { value = newValue }
rememberUpdatedState
function is the same as using remember
with mutableState
to trigger recomposition when value
changes.
@Composable
private fun Calculation(input: Int) {
val rememberUpdatedStateInput by rememberUpdatedState(input)
val rememberedInput = remember { input }
Text("updatedInput: $rememberUpdatedStateInput, rememberedInput: $rememberedInput")
}
var myInput by remember {
mutableStateOf(0)
}
OutlinedButton(
onClick = {
myInput++
}
) {
Text("Increase $myInput")
}
Calculation(input = myInput)
This is a very basic example to show how values
from remember
and rememberUpdatedState
change.
A more practical example is with lambdas.
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:
@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 */
}
In this example LaunchedEffect
is invoked once but this LandingScreen function can be recomposed and might require you to change onTimeOut
, so using rememberUpdatedState
makes sure that the latest onTimeout
is called after delay.
Upvotes: 33
Reputation: 125
Jeel gave a great answer so I will just add some sample code.
I've created an example that demonstrates the effect of rememberUpdatedState()
on LaunchedEffect
with and without it. The example is very simple:
the user schedules a message that will be passed inside a lambda to LaunchedEffect
.
After a while the user creates another message that will be passed inside a lambda to the exact same LaunchedEffect
as the previous message.
In case the user chooses to use rememberUpdatedState()
- the LaunchedEffect
will get updated with the new lambda. In case they don't - LaunchedEffect
will run with the initial lambda and will ignore further recompositions.
Here is the code that performs the "decision making" and ultimately either updates LaunchedEffect
or doesn't:
@Composable
fun ShowToast(
useRememberUpdatedState: Boolean,
message: (() -> String)? = null
) {
val context = LocalContext.current
var actualTrueMessage: State<(() -> String)?>? = null
if (useRememberUpdatedState) {
actualTrueMessage = rememberUpdatedState(message)
}
LaunchedEffect(Unit) {
delay(MESSAGE_DELAY)
Toast.makeText(
context,
actualTrueMessage?.value?.invoke() ?: message?.invoke(),
Toast.LENGTH_SHORT
).show()
}
}
Please note that in my example I'm specifically launching LaunchedEffect
with a Unit
key so that it is launched exactly once for composition and recompositions.
Here is what it looks like:
Upvotes: 8
Reputation: 12118
The difference between remember and rememberUpdatedStates are:
remember
Remember the value produced by calculation. calculation will only be evaluated during the composition. Recomposition will always return the value produced by composition.
When you use remember, every consecutive calls to recomposition will only return same value that was computed initially during first call to remember. You can consider this as an read-only state that you can not update on future reference while recomputing will reference to initial evaluation.
rememberUpdatedStates
remember a mutableStateOf and update its value to newValue on each recomposition of the rememberUpdatedState call.
rememberUpdatedState should be used when parameters or values computed during composition are referenced by a long-lived lambda or object expression. Recomposition will update the resulting State without recreating the long-lived lambda or object, allowing that object to persist without cancelling and resubscribing, or relaunching a long-lived operation that may be expensive or prohibitive to recreate and restart.
Here, it is expected that sometimes your calculation can take a while and computation may be considerable slow. In such cases, you're provided with latest value rather than lambda that will take impact on every recomposition so that you can have reference to the latest value produced by calculation.
By using this method, you make sure that your UI is updated by every recomposition without recreating long-lived lambdas or relaunching long-lived operations that you may have during remember method's lambda callbacks.
Upvotes: 25