State Preservation

One of the biggest frustrations with full rebuilds is losing your app state: the screen you navigated to, the form you were filling out, the scroll position in a long list. HotSwan preserves your state across reloads through targeted recomposition that tries the least disruptive approach first.

This page explains how each strategy works, what state survives at each level, and how you can structure your code to maximize preservation.

Recomposition strategy

After the runtime swaps the changed classes in memory, HotSwan needs to trigger a Compose recomposition so the UI picks up the new code. The question is how to trigger recomposition in a way that preserves as much state as possible.

HotSwan uses a three tier strategy. It tries the first tier, and only falls back to the next tier if the previous one is unavailable or fails:

  1. Targeted recomposition: recomposes only the affected composable scopes in place.
  2. Composition reset: disposes and recreates all compositions from scratch.
  3. Restart Activity: recreates the entire Activity.

In practice, targeted recomposition succeeds for the vast majority of changes. You will rarely see the fallback tiers trigger during normal development.

Primary: Targeted recomposition

HotSwan identifies which composable scopes are affected by the code change and invalidates only those scopes. On the next frame, the Compose runtime re-executes the affected composable functions with the new code. The key property is that compositions are not disposed or recreated. The slot table remains intact.

Because the slot table is untouched, all state stored in it survives: plain remember{} values, rememberSaveable{} values, navigation state, scroll position, lazy layout items, and dialog or bottom sheet state. Nothing is reset.

This works even when composables are reordered. The compiler plugin ensures that each composable's state is tracked by its identity rather than its position in the source code, so the runtime preserves state correctly regardless of ordering.

Fallback: Composition reset

If targeted recomposition is not available (for example, if the Compose runtime version does not support it), HotSwan falls back to a full composition reset. This disposes all active compositions and recreates them from scratch with fresh slot tables.

Because slot tables are recreated, plain remember{} values are lost. However, state stored in rememberSaveable{} is backed by the SavedStateRegistry, which persists across composition disposal. ViewModel instances survive because they are scoped to the ViewModelStore, which is independent of the composition lifecycle. Navigation back stacks survive because they are managed by the NavController, not the slot table.

Last resort: Restart Activity

If composition reset also fails, HotSwan restarts the Activity as a last resort. This destroys and recreates the entire Activity, similar to a configuration change (like rotating the device).

SavedInstanceState is preserved because Android serializes it before destruction. ViewModel instances survive because they are scoped to the ViewModelStore owner, which outlives Activity recreation. However, everything else is reset: composition state, scroll position, dialog state, and transient UI state.

What survives each level

State typeTargetedResetRestart
remember{}
rememberSaveable{}
ViewModel
Navigation back stack
Scroll position
Lazy layout items
Dialog state
SavedInstanceState

Tips for maximizing state preservation

Since targeted recomposition is the primary path and preserves all state, most of the time you do not need to worry about state loss. But if you want to be defensive:

  • Use rememberSaveable instead of remember for state that matters. It survives all three tiers.
  • Keep important state in ViewModels. ViewModel instances survive all tiers, so state stored there is always preserved.
  • Avoid structural changes during iteration. Adding new functions or changing data classes triggers a full build, which restarts the app. Save structural changes for when you are done iterating on the UI.