I am using MutableTransitionState
to support animation for a Popup in my Compose app. The problem is, the Popup disappears after a configuration change such as screen rotation because I am using expandedState = remember { MutableTransitionState(false) }
.
I would like the Popup to survive a configuration change to improve user experience. In order to achieve this, I've written the following code:
data class Holder(var expandedState: MutableTransitionState<Boolean>)
val expandedSaver = Saver<Holder, MutableTransitionState<Boolean>>(
save = { it.expandedState},
restore = { Holder(it) }
)
@Composable
public fun SomeComposable() {
val holder = rememberSaveable(stateSaver = expandedSaver) {
mutableStateOf(Holder(MutableTransitionState(false)))
}
...
}
However, when I run the code, I get the usual error complaining that I should consider implementing a custom Saver for this class
despite already using a custom saver!
I am using MutableTransitionState
to support animation for a Popup in my Compose app. The problem is, the Popup disappears after a configuration change such as screen rotation because I am using expandedState = remember { MutableTransitionState(false) }
.
I would like the Popup to survive a configuration change to improve user experience. In order to achieve this, I've written the following code:
data class Holder(var expandedState: MutableTransitionState<Boolean>)
val expandedSaver = Saver<Holder, MutableTransitionState<Boolean>>(
save = { it.expandedState},
restore = { Holder(it) }
)
@Composable
public fun SomeComposable() {
val holder = rememberSaveable(stateSaver = expandedSaver) {
mutableStateOf(Holder(MutableTransitionState(false)))
}
...
}
However, when I run the code, I get the usual error complaining that I should consider implementing a custom Saver for this class
despite already using a custom saver!
1 Answer
Reset to default 1Using rememberSaveable
on a MutableTransitionState won't work.
You use the rememberSaveable
overload with the stateSaver
parameter. That implies that the value is a MutableState which is unnecessary. You probably meant to use the overload using the saver
parameter.
But that won't work either. You would have to construct a MutableTransitionState off the single expanded
property of your Holder, but a MutableTransitionState has both a currentState
and a targetState
property. You would need to save both if you want to restore the MutableTransitionState to what it was before. Furthermore, there is a third property isRunning
which is internal and cannot be accessed by your code, so you will never be able to restore it.
Bottom line: You cannot fully save and restore a MutableTransitionState. After a configuration change at least some parts of the state are irrevocably lost.
If you are only interested in saving the targetState
then the second approach you had in your original question is the way to go. You just need to update the MutableTransitionState's targetState when expanded
changes1:
var expanded by rememberSaveable { mutableStateOf(false) }
val expandedState = remember { MutableTransitionState(expanded) }
.apply { targetState = expanded }
The rest of your code shouldn't touch expandedState
directly anymore, so it would be best to never save the MutableTransitionState in a variable in the first place. That would prevent accidently doing something like expandedState.targetState = !expandedState.targetState
. When you only need it for an AnimatedVisibility you could rewrite it like this:
var expanded by rememberSaveable { mutableStateOf(false) }
AnimatedVisibility(
visibleState = remember { MutableTransitionState(expanded) }
.apply { targetState = expanded },
// ...
)
Now only the expanded
variable is accessible by the rest of your code (and it survives configuration changes), the MutableTransitionState is hidden but will always be updated accordingly when expanded
changes.
1 expanded
is used twice here. The first usage is needed when the MutableTransitionState
is constructed to set the initial value. It is behind a remember
, so it is only executed once (until a configuration change takes place, then it is executed again and a new MutableTransitionState is created).
The second usage is needed to actually start a transition. Whenever the MutableState expanded
changes, a recomposition occurs. The remember
lambda is skipped, but the apply
is executed again, setting targetState
to the new value.