Search code examples
androidandroid-jetpack-compose

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

  • 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.