Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start using UiSavedStateRegistry to preserve screen "view" state. #25

Merged
merged 1 commit into from
Apr 19, 2020

Conversation

zach-klippenstein
Copy link
Collaborator

@zach-klippenstein zach-klippenstein commented Apr 18, 2020

Currently screens' states are saved as long as the Backstack is in the composition
by being kept entirely alive. This is not efficient, it's equivalent to keeping
all views in the backstack attached at all times. Ideally we would only keep screens
in the composition while they are actually visible, and remove all hidden screens as
soon as possible. Unfortunately, until dev08, the only way to preserve view state
was to keep these screens composed.

Dev08 introduces rememberSavedInstanceState and savedInstanceState composables.
They mirror remember and state, but will restore their values from a
UiSavedStateRegistry when they are first composed. This is the standard API
for saving view state, much like onSavedInstanceState in the legacy framework,
and so the backstack should support it.

This change does a few things:

  • When a screen is not longer visible, remove it from the composition. Note that
       the ScreenWrapper remains in the composition as long as the screen's key is
       in the active backstack, but when a screen stops being visible, we stop calling
       the drawScreen function for it. Note that this means we no longer need to set
       the "hidden" semantics property on screens that aren't visible.
  • The ScreenWrapper holds a map of saved state values. This map, in turn, gets
       saved and restored to the parent's UiSavedStateRegistry, so it can survive activity
       recreations etc.
  • Each screen is wrapped with its own UiSavedStateRegistry that is scoped to just
       that screen. This registry is asked to save values to the above map when the screen
       is going to be hidden, and is re-created to restore from the cached values when it
       is shown.

The keys to making this work are:

  • Scoping ScreenWrappers to the presence of a screen in the backstack, even though
       the screen's actual composables are only alive when the screen is visible.
  • The mechanism that the saved state machinery uses to key values is derived from the
       positional memoization keys. The only way this works is because those keys remain
       static even when a composable is removed and re-added later. Because that happens,
       and because those keys are globally unique (at least within the composition), the
       individual screen registries don't need to do any scoping of their keys, and we don't
       need to worry about somehow turning the screen keys (T) into state keys (String).
       The one thing I haven't verified is if these keys stay constant even if items in the
       hidden backstack are reordered, but I expect they will since @Pivotal makes use of
       this same keying infrastructure.

Currently screens' states are saved as long as the Backstack is in the composition
by being kept entirely alive. This is not efficient, it's equivalent to keeping
all views in the backstack attached at all times. Ideally we would only keep screens
in the composition while they are actually visible, and remove all hidden screens as
soon as possible. Unfortunately, until dev08, the only way to preserve view state
was to keep these screens composed.

Dev08 introduces `rememberSavedInstanceState` and `savedInstanceState` composables.
They mirror `remember` and `state`, but will restore their values from a
`UiSavedStateRegistry` when they are first composed. This is the standard API
for saving view state, much like `onSavedInstanceState` in the legacy framework,
and so the backstack should support it.

This change does a few things:
 - When a screen is not longer visible, remove it from the composition. Note that
   the `ScreenWrapper` remains in the composition as long as the screen's key is
   in the active backstack, but when a screen stops being visible, we stop calling
   the `drawScreen` function for it. Note that this means we no longer need to set
   the "hidden" semantics property on screens that aren't visible.
 - The `ScreenWrapper` holds a map of saved state values. This map, in turn, gets
   saved and restored to the parent's `UiSavedStateRegistry`, so it can survive activity
   recreations etc.
 - Each screen is wrapped with its own `UiSavedStateRegistry` that is scoped to just
   that screen. This registry is asked to save values to the above map when the screen
   is going to be hidden, and is re-created to restore from the cached values when it
   is shown.

The keys to making this work are:
 - Scoping `ScreenWrapper`s to the presence of a screen in the backstack, even though
   the screen's actual composables are only alive when the screen is visible.
 - The mechanism that the saved state machinery uses to key values is derived from the
   positional memoization keys. The only way this works is because those keys remain
   static even when a composable is removed and re-added later. Because that happens,
   and because those keys are globally unique (at least within the composition), the
   individual screen registries don't need to do any scoping of their keys, and we don't
   need to worry about somehow turning the screen keys (`T`) into state keys (`String`).
   The one thing I haven't verified is if these keys stay constant even if items in the
   hidden backstack are reordered, but I expect they will since `@Pivotal` makes use of
   this same keying infrastructure.
@zach-klippenstein zach-klippenstein merged commit 9db16d1 into master Apr 19, 2020
@zach-klippenstein zach-klippenstein deleted the zachklipp/saved-state branch April 19, 2020 20:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant