When using navigation compose `rememberSaveable` s...
# compose
j
When using navigation compose
rememberSaveable
saves against the nav destination registry. It would be good to be able to call
rememberSaveable
against a SaveableStateRegistry higher up (i.e. a parent graph). Is this possible?
d
I think an alternative is use a ViewModel scoped to the parent graph
j
There doesn’t seem to be a straightforward way to save snapshot state with a ViewModels
SavedStateHandle
.
rememberSaveable
is already set up perfectly for saving snapshot state so I’ve been hoping to stick with it
z
You can provide your own
SaveableStateRegistry
to
rememberSaveable
via composition locals
j
I’m not sure how I can provide my own since It’s not possible to access a @Composable context at a graph level in navigation compose
The first entry point of @Compose code is whichever destination you land on first, at which point the
SaveableStateRegistry
belongs to the destination
z
Right, but you could wrap each destination with a composition local provider
j
How would multiple destinations access the same registry
The only layer up would be defining a new
SaveableStateHolder
above the NavHost
z
You could get to it through a view model as suggested above, yea
You'd still have to create a SaveableStateRegistry from the handle yourself i think, but that should be doable
j
Wait, so where is it defined?
If it’s defined on the view model it wont survive process death
i
There's no composition happening at the graph level. What exactly are you trying to do?
j
Share snapshot state across destinations with
rememberSaveable
i
This sounds like perhaps an XY problem: https://xyproblem.info/ - can you explain it in terms of your concrete use case and an example?
j
Sure, it’s similar to a form builder. We have a single state object which holds a large number of snapshot state properties. Multiple destinations can update these properties. We currently hold the object on a VM scoped to a navigation graph, but the issue is that none of the properties survive process death
i
Yeah, like I said, there's no composition at the graph level. That means no
SaveableStateRegistry
nor any
rememberSaveble
.
SavedStateHandle
actually has APIs to support custom saving , which lets you write a utility method like:
Copy code
fun <T : Any> SavedStateHandle.saveableMutableStateOf(
    key: String,
    saver: Saver<T, out Any> = autoSaver(),
    init: () -> T
): MutableState<T> {
    // To be honest, I'm not sure why rememberSaveable does this
    // vs just have the saver of type Saver<T, Any>
    @Suppress("UNCHECKED_CAST")
    (saver as Saver<T, Any>)

    // value is restored using the SavedStateHandle or created via [init] lambda
    val value = mutableStateOf(run {
        val restored = get<Bundle>(key)?.get("value")?.let {
            saver.restore(it)
        }
        restored ?: init()
    })

    // Hook up saving the MutableState to the SavedStateHandle
    setSavedStateProvider(key) {
        val saverScope = SaverScope { true }
        with (saver) {
            bundleOf("value" to saverScope.save(value.value))
        }
    }
    return value
}
Which lets you replace a
by mutableStateOf()
within your ViewModel with `by savedStateHandle.saveableMutableStateOf()`:
Copy code
class SavingTestViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
    var saved by savedStateHandle.saveableMutableStateOf("key") { "initialState" }
}
Basically bridging the gap between
Saver
and
SavedStateHandle
j
Ok that makes sense, thanks
@Ian Lake It would be good to understand why there is no composition happening at the graph level? It seems like this could provide a way to share state between destinations without going through a VM. It would also help with the common complaint of animating “global widgets” (bottom/top bars) in sync with screen transitions since we could just render them for subgraphs instead of globally