Reputation: 352
Consider we have the following structure in a toggling implementation scenario:
@Composable
fun RootComposable() {
var someAuxToggle by remember { mutableStateOf(false) }
if (someAuxToggle) {
FirstComposable { someAuxToggle = !someAuxToggle }
} else {
SecondComposable { someAuxToggle = !someAuxToggle }
}
}
@Composable
fun FirstComposable(auxCallback: () -> Unit) {
val myRandomNumber by remember { mutableStateOf(Random.nextInt(100)) }
Button(onClick = { auxCallback() }) {
Text(text = "I'm the FirstComposable.\nMy random is: $myRandomNumber")
}
}
@Composable
fun SecondComposable(auxCallback: () -> Unit) {
val myRandomNumber by remember { mutableStateOf(Random.nextInt(100)) }
Button(onClick = { auxCallback() }) {
Text(text = "I'm the SecondComposable.\nMy random is: $myRandomNumber")
}
}
Doing this, the toggling works, the composables are correclty called, however their own state changes, once I'm "recreating" then. This is also valid if those buttons was placed inside other top composables and these top composables be called.
Now, if we implement this in a little hacky way the state keeps alive (the random numbers are not regenerated):
@Composable
fun RootComposable() {
var someAuxToggle by remember { mutableStateOf(false) }
Box(
if (someAuxToggle)
Modifier.wrapContentSize()
else
Modifier.size(0.dp)
) {
FirstComposable { someAuxToggle = !someAuxToggle }
}
Box(
if (someAuxToggle)
Modifier.size(0.dp)
else
Modifier.wrapContentSize()
) {
SecondComposable { someAuxToggle = !someAuxToggle }
}
}
@Composable
fun FirstComposable(auxCallback: () -> Unit) {
val myRandomNumber by remember { mutableStateOf(Random.nextInt(100)) }
Button(onClick = { auxCallback() }) {
Text(text = "I'm the FirstComposable.\nMy random is: $myRandomNumber")
}
}
@Composable
fun SecondComposable(auxCallback: () -> Unit) {
val myRandomNumber by remember { mutableStateOf(Random.nextInt(100)) }
Button(onClick = { auxCallback() }) {
Text(text = "I'm the SecondComposable.\nMy random is: $myRandomNumber")
}
}
Note that in this implementation both buttons are being called inside root but the toggling was made by switching their "visibility", via setting their sizes.
Also note that the buttons composable are really the same component, but my idea is just demonstrate the state behaviour.
My question is about the best way to keep the state if we call a composable again, like in the first code; if there are a simple way to do it or if we really need to store those states in some top level fields or something like this.
Also, is the second approach too expensive in terms of performance? I think yes, because both composable are "turned on" all the time, then if we have composables that makes hard work (e.g., via side effects or even background tasks) then performance can be lowered.
Anyway, I hope we find a solution to that.
Upvotes: 1
Views: 1175
Reputation: 6197
It depends on the scope of how you want to recreate
or restore
a state, you can either hoist it in a viewmodel
where a state is dependent on its lifecycle, or inside a rememberSaveable
where you have to define a Saver
for it to save
and restore
the state. So far, I only consider using rememberSaveable
when I only want to survive and restore states during configuration changes (i.e light to dark, screen rotate), on the other hand, I hoist states via ViewModel
when functionalities of a composable
depends on much deeper functionalities (i.e repository/network calls, heavy business use-cases)
Also Both of the implementation will trigger the entire re-composition of RootComposable
as it observes a mutableState
if (someAuxToggle)
...
...
I'm not quite sure though if the second one has a significant performance hit assuming this is only the scope of your actual code.
I'd recommend watching this once in a while as a refresher when making hoisting
decisions A Compose state of mind: Using Jetpack Compose's automatic state observation
Upvotes: 2