Reputation: 362
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:
SnapshotStateList
.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.
Upvotes: 26
Views: 25318
Reputation: 88447
There should be no data inside remember
or rememberSaveable
that you are afraid of losing: it's gonna be destroyed as soon as your Composable
disappears (goes out of composition, to be precise). Consider using view models in this case.
If you are still interested in storing mutableStateListOf
inside rememberSaveable
, I suggest that you follow your error recommendation and create a Saver
for SnapshotStateList
:
@Composable
fun <T: Any> rememberMutableStateListOf(vararg elements: T): SnapshotStateList<T> {
return rememberSaveable(saver = snapshotStateListSaver()) {
elements.toList().toMutableStateList()
}
}
private fun <T : Any> snapshotStateListSaver() = listSaver<SnapshotStateList<T>, T>(
save = { stateList -> stateList.toList() },
restore = { it.toMutableStateList() },
)
Then, you can use it like this:
val names = rememberMutableStateListOf<String>()
LaunchedEffect(Unit) {
names.add("Bill")
}
Text(names.joinToString { it })
The expected behaviour for 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 an item. You should only do that inside side-effects, like I did here with LaunchedEffect
, or callbacks, like onClick
.
Note that saveableMutableStateListOf
is 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.
Upvotes: 37
Reputation: 659
Example:
var snackbars by rememberSaveable(
stateSaver = listSaver(
save = {
buildList {
add(it.size)
it.forEach {
add(it.id)
add(it.message)
add(it.actionLabel)
add(it.withDismissAction)
add(it.duration)
add(it.delay)
}
}
},
restore = { list: List<*> ->
val size = list[0] as Int
val snackbarSize = 6
buildList {
repeat(size) {
val i = it * snackbarSize
add(
Snackbar(
id = list[i + 1] as Long,
message = list[i + 2] as String,
actionLabel = list[i + 3] as String?,
withDismissAction = list[i + 4] as Boolean,
duration = list[i + 5] as SnackbarDuration,
delay = list[i + 6] as Long,
)
)
}
}
}
),
) { mutableStateOf(listOf()) }
Upvotes: 0
Reputation: 11
I found the following solution for myself:
I create a saver function in which I convert a list of strings into a string separated by '\n' and vice versa:
fun saverListOfString(): Saver <MutableList<String>, String> = Saver(
save = {
var saved = ""
it.forEachIndexed { index, s ->
if (index > 0) saved += "\n"
saved += s
}
saved
},
restore = { restored ->
val restoredList = restored.lines()
val list = mutableStateListOf<String>()
restoredList.forEach { list.add(it) }
list
}
)
I save the list of strings in Saveable like this:
val stringList = rememberSaveable(saver = saverListOfString()) { mutableStateListOf() }
Upvotes: -2
Reputation: 33
I came up with something like this
val selectedLists: MutableList<String?> = rememberSaveable(
saver = listSaver<MutableList<String?>, String?>(
save = {
if (it.isNotEmpty()) {
it.toList()
} else {
listOf<String?>(null)
}
},
restore = {
it.toMutableStateList()
}
)
) {
mutableStateListOf(*viewState.selectedLists.map { it.name }.toTypedArray())
}
Upvotes: 1
Reputation: 6863
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
Upvotes: 0