'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:

  1. Actually implementing a saver for SnapshotStateList.
  2. 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