https://kotlinlang.org logo
#compose
Title
# compose
t

Timo Drick

09/02/2020, 11:47 AM
I try to get SavedInstanceState working in my custom Navigation framework. I am able to save state when dispose one screen and i will create a new UiSavedStateRegistry with restored values and provide it to the child composable. Unfortunatly the child composable with a
Copy code
var checkBoxState by savedInstanceState { false }
Is not using the provided Registry :-(
Copy code
log("${item.data} saved state: $${item.savedState}")
    val restoredRegistry by remember(item.savedState) {
        log("${item.data} Create new registry with: ${item.savedState}")
        mutableStateOf(UiSavedStateRegistry(restoredValues = item.savedState, canBeSaved = { true }))
    }
    Providers(UiSavedStateRegistryAmbient provides restoredRegistry) {
        log("${item.data} ${UiSavedStateRegistryAmbient.current}")
        children(item.data)
        onDispose {
            val saved = restoredRegistry.performSave()
            log("${item.data} onDispose saved: $saved")
            item.savedState = saved
        }
    }
Maybe someone can point me into the right direction what is wrong?
z

Zach Klippenstein (he/him) [MOD]

09/02/2020, 12:09 PM
You don't need to store your registry in a
MutableState
since you're not changing the reference.
t

Timo Drick

09/02/2020, 12:12 PM
yes you are right. So i just checked that the UiSavedStateRegistryAmbient.current in the child composable is indeed the correct instance. Could it be possible that the savedInstanceState varaibles get a new hash key when the composition is created again?
because the registry contains the hashes from the disposed composition and when it gets recreated maybe the hash will change.
Than is it possible at all to get this working?
z

Zach Klippenstein (he/him) [MOD]

09/02/2020, 12:14 PM
You should debug to verify this is what's happening, but I believe
onDispose
callbacks are invoked deepest-first, so your
onDispose
will be executed after all the `savedInstanceState`s in
children
are disposed, and they will remove their state providers from the registry on disposal. So by the time you execute
performSave
, the registry will be empty. See what
ChildSavedStateRegistry
does here: https://github.com/zach-klippenstein/compose-backstack/blob/aa24271797de6f1fcae73f58c78e6646be75f5eb/compose-backstack/src/main/java/com/zachklipp/compose/backstack/Backstack.kt#L247
t

Timo Drick

09/02/2020, 12:16 PM
No perfromSave works fine. It contains expected values.
[Transition.kt:170] TransitionKt$Crossfade3$showItem$1$1.invoke : com.example.composeplayground.MainUIScreen$StartScreen@5f7ad68 saved state: ${1711518371=[true], 1473374894=[Test]}
[Transition.kt:176] TransitionKt$Crossfade3$showItem$1$1$1.invoke : com.example.composeplayground.MainUIScreen$StartScreen@5f7ad68 androidx.compose.runtime.savedinstancestate.UiSavedStateRegistryImpl@2f9636f
[MainActivity.kt:113] MainActivity$MainView$1.invoke : Transition Render main com.example.composeplayground.MainUIScreen$StartScreen@5f7ad68
[Transition.kt:170] TransitionKt$Crossfade3$showItem$1$1.invoke : com.example.composeplayground.MainUIScreen$StartScreen@5f7ad68 saved state: ${1711518371=[true], 1473374894=[Test]}
[Transition.kt:176] TransitionKt$Crossfade3$showItem$1$1$1.invoke : com.example.composeplayground.MainUIScreen$StartScreen@5f7ad68 androidx.compose.runtime.savedinstancestate.UiSavedStateRegistryImpl@2f9636f
[MainActivity.kt:113] MainActivity$MainView$1.invoke : Transition Render main com.example.composeplayground.MainUIScreen$StartScreen@5f7ad68
[Transition.kt:180] TransitionKt$Crossfade3$showItem$1$1$1$1.invoke : com.example.composeplayground.MainUIScreen$StartScreen@5f7ad68 onDispose saved: {1711518371=[true], 1473374894=[Test], 1629430880=[true], 926054518=[Test]}
[MainActivity.kt:110] MainActivity$MainView$1$1$1.invoke : Transition main dispose: com.example.composeplayground.MainUIScreen$StartScreen@5f7ad68
And also after going back to the screen the UiSavedStateRegistry recreation is triggered and filled with the correct map
So in my opinion the only thing what could be wrong is that the keys change when the composition is disposed and than created again
z

Zach Klippenstein (he/him) [MOD]

09/02/2020, 12:21 PM
because the registry contains the hashes from the disposed composition and when it gets recreated maybe the hash will change.
This shouldn't happen AFAIK, but did you verify this?
t

Timo Drick

09/02/2020, 12:21 PM
no just a second i will check
Yes the hash changes 😞 At least of the root composition.
Copy code
currentComposer.currentCompoundKeyHash
Is not the same when the same composable is created again
That means for me if you want to provide back navigation i must not dispose the compositions in the backstack correct?
z

Zach Klippenstein (he/him) [MOD]

09/02/2020, 12:28 PM
Do you have some control flow somewhere below this code that is executing a different branch on restore? The keys should be constant relative to source code location "path" and the use of any
key
composables.
t

Timo Drick

09/02/2020, 12:29 PM
Yes it will change because i do have a transition animation. That will switch between component A and B
And when you go back A and B are switched
z

Zach Klippenstein (he/him) [MOD]

09/02/2020, 12:30 PM
That sounds like the problem then. Your transition logic should probably use
key
to fix this
t

Timo Drick

09/02/2020, 12:32 PM
i do have this:
Copy code
@Composable
fun showItem(item: CrosstransitionAnimationItem<SavedStateData<T>>?) {
    item?.let { (item, transition) ->
        key(item) {
            transition(item) {
                log("${item.data} saved state: $${item.savedState}")
                val restoredRegistry by remember(item.savedState) {
                    log("${item.data} Create new registry with: ${item.savedState}")
                    mutableStateOf(UiSavedStateRegistry(restoredValues = item.savedState, canBeSaved = { true }))
                }
                Providers(UiSavedStateRegistryAmbient provides restoredRegistry) {
                    log("${item.data} ${UiSavedStateRegistryAmbient.current}")
                    children(item.data)
                    onDispose {
                        val saved = restoredRegistry.performSave()
                        log("${item.data} onDispose saved: $saved")
                        item.savedState = saved
                    }
                }
            }
        }
    }
}
Stack {
    ts.invalidate = invalidate
    showItem(item = ts.itemA)
    showItem(item = ts.itemB)
}
Hmm maybe because transition changes. (Different forward and back transition)
z

Zach Klippenstein (he/him) [MOD]

09/02/2020, 12:35 PM
What does
transition()
do?
t

Timo Drick

09/02/2020, 12:38 PM
It is a composable which contains a Stack with a Modifier to do some animation.
Ok maybe i need to start with a much simpler navigation system without animation first.
I will try to create an simpler sample
z

Zach Klippenstein (he/him) [MOD]

09/02/2020, 12:52 PM
Sounds like it’s the right thing to do, might help to print the compound key hash at each level to verify where it’s changing?
t

Timo Drick

09/02/2020, 12:58 PM
Yes the simple implementation works 😄
Copy code
@Composable
fun <T: Any>NavigationSwitcher(transition: Transition<T>,
                       children: @Composable() (T) -> Unit) {
    val item = transition.data

    key(item.data) {
        val restoredRegistry by remember(item.savedState) {
            log("${item.data} Create new registry with: ${item.savedState}")
            mutableStateOf(UiSavedStateRegistry(restoredValues = item.savedState, canBeSaved = { true }))
        }
        Providers(UiSavedStateRegistryAmbient provides restoredRegistry) {
            children(item.data)
            onDispose {
                val saved = restoredRegistry.performSave()
                log("${item.data} onDispose saved: $saved")
                item.savedState = saved
            }
        }
    }
}
Of course now i do not have any animation when switching views
But i think i can figure that out now.
@Zach Klippenstein (he/him) [MOD] thank you very much for helping and discussing this with me. That helped a lot. 👍
Also the solution with animation now works. The trick was to change the key(item) -> key(item.data)
Because the item contains also the stored registry data. And this is of course than different when it contains data.
😀🎉
z

Zach Klippenstein (he/him) [MOD]

09/02/2020, 1:07 PM
aha!
happy to rubber duck 😂
1
t

Timo Drick

09/02/2020, 1:29 PM
z

Zach Klippenstein (he/him) [MOD]

09/12/2020, 6:04 PM
2 Views