'Using rememberSaveable with mutableStateListOf
I'm trying to add a mutable list of parcelable objects to my composable. I also want to be able to add objects to and remove objects from it.
Currently I'm using something like this:
val names = remember { mutableStateListOf<String>() }
names.add("Bill")
names.remove("Bill")
Now I want this list to survive configuration change, therefore it's perhaps a good idea to use rememberSaveable. Perhaps something like this:
val names = rememberSaveable { mutableStateListOf<String>() }
names.add("Bill")
names.remove("Bill")
But this does not work, it throws the following exception:
androidx.compose.runtime.snapshots.SnapshotStateList cannot be saved using the current SaveableStateRegistry. The default implementation only supports types which can be stored inside the Bundle. Please consider implementing a custom Saver for this class and pass it to rememberSaveable().
This means, that SnapshotStateList (the result of mutableStateListOf) is not saveable.
So far I can think of few ways how to work around this:
- Actually implementing a saver for
SnapshotStateList. - Using something like
val namesState = rememberSaveable { mutableStateOf(listOf<String>()) }. This does indeed work flawlessly, however updating the list requires setting the value, which is both slow and inconvenient (e.g.namesState.value = namesState.value + "Joe"for just adding a single element).
Both these ways seem too complicated for a seemingly small task. I wonder what is the best way to do what I want. Thanks.
Solution 1:[1]
There should be no data inside remember or rememberSaveable that you are afraid of losing: it's gonna be destroyed as soon as your view disappears. Consider using view models in this case.
If you still interested in storing mutableStateListOf inside rememberSaveable, I suggest you following your error recommendation and creating a Saver for SnapshotStateList:
@Composable
fun <T: Any> rememberMutableStateListOf(vararg elements: T): SnapshotStateList<T> {
return rememberSaveable(
saver = listSaver(
save = { stateList ->
if (stateList.isNotEmpty()) {
val first = stateList.first()
if (!canBeSaved(first)) {
throw IllegalStateException("${first::class} cannot be saved. By default only types which can be stored in the Bundle class can be saved.")
}
}
stateList.toList()
},
restore = { it.toMutableStateList() }
)
) {
elements.toList().toMutableStateList()
}
}
Then you can use it like this:
val names = rememberMutableStateListOf<String>()
LaunchedEffect(Unit) {
names.add("Bill")
}
Text(names.joinToString { it })
Expected behaviour of this sample: each time you rotate your device - one more element gets added.
Don't use any state modifications inside the composable like you did when you added and removed item. You should only do that inside side-effects, like I'm doing here with LaunchedEffect, or callbacks, like onClick.
Note that saveableMutableStateListOf it still limited to Bundle-saveable types, like String, Int, etc. If you need to store a custom type inside, you will need to modify Saver to save/recreate it too.
Solution 2:[2]
The kind of data you are storing must be saveable in a Bundle, or else, you must pass in a custom-saver object. Just look at the docs for state and maybe specifically for rememberSaveable
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Richard Onslow Roper |
