Skip to content

AppState spike#2978

Draft
StylianosGakis wants to merge 17 commits into
developfrom
spike/appstate-hoisted-navigator
Draft

AppState spike#2978
StylianosGakis wants to merge 17 commits into
developfrom
spike/appstate-hoisted-navigator

Conversation

@StylianosGakis

@StylianosGakis StylianosGakis commented Jun 8, 2026

Copy link
Copy Markdown
Member

Hoist nav state into AppState spike

Uses snapshot builds, not to be merged

StylianosGakis and others added 17 commits June 7, 2026 13:20
Previously, any navigation originating from a presenter had to go through a
flag-dance: the presenter set a Boolean/nullable flag in UiState, the UI
observed it in a LaunchedEffect, navigated, then fired an event back to reset
the flag so recomposition/config-change wouldn't re-trigger it. This existed
only because presenters had no way to navigate. Now that Backstack is an
app-scoped injectable, presenters navigate directly: the flag field, its reset
event, and the UI observer are removed across the board.

This clarifies what triggers navigation — it now lives at the point of the
decision (event handler / result fold) instead of being smeared across UiState,
a LaunchedEffect, and an event round-trip.

Affected feature modules: movingflow, choose-tier, addon-purchase,
terminate-insurance, edit-coinsured, remove-addons, payout-account,
travel-certificate, insurance-certificate, help-center, payments, chip-id.

Navigation-sensitive cases preserved exactly:
- edit-coinsured: triage and edit presenters are each shared between two
  entries, so the pop anchor is resolved at runtime via findLastOrNull<>()
  (navigateFromTriage / navigateToEditCoInsuredSuccess).
- movingflow: SelectContract keeps the conditional pop
  (currentHomeAddresses.size < 2) and Summary keeps its two-step terminal pop
  (popUpTo<SelectContractForMovingKey> then navigateAndPopUpTo<HousingTypeKey>).
- insurance-certificate uses inclusive=true while travel-certificate uses
  inclusive=false — intentionally different back behavior, left untouched.
- terminate-insurance: both Terminated and Deleted route to TerminationSuccessKey
  via navigateAndPopUpTo<TerminateInsuranceKey>(inclusive=true).
- help-center: same-module quick links (FirstVet, SickAbroad) navigate directly,
  but cross-module OuterDestination intentionally retains the minimal flag dance
  since cross-module targets must stay injected lambdas.

Cross-module navigation, pure UI-button navigation (navigateUp/popBackstack/
exitFlow/goHome), and GlobalSnackBarState overlays are deliberately left as-is.
To navigate directly to another feature's entry point, a presenter must be
able to name that feature's nav key. Today every key is locked inside its
owning feature module, and the feature-can't-depend-on-feature rule forbids
reaching it, which forces a flag-dance routed through the app module.

This enables a `feature-x-navigation` sister module that hosts only the keys
of feature-x meant to be reached from other features:

- HedvigGradlePlugin exempts `-navigation` modules from the feature dependency
  check, so any feature can depend on them while the owning feature keeps its
  entries/screens and internal-only keys private.
- NavKeySerializerProcessor no longer derives the generated provider's name
  from the package alone (a feature and its `-navigation` sister legitimately
  share a package, e.g. `.navigation`, which would collide). The name now folds
  in a stable hash of the module's key FQNs, which are globally unique by
  construction since a key class is declared in exactly one module. This keeps
  the processor self-contained with no Gradle module name to inject.
Replace the field-setting navigation dance (presenter sets a UiState flag
-> UI LaunchedEffect -> injected lambda -> central when mapping intent to
key) with presenters that add concrete nav keys to the Backstack directly.

Introduce feature-x-navigation sister modules hosting only the keys meant
to be reached cross-feature, which the build plugin exempts from the
feature->feature dependency ban. Rewrite HelpCenterPresenter to map every
quick-link destination straight to a key, dropping destinationToNavigate
and the ClearNavigation event, and remove the central when lambda in the
app's HedvigEntryProvider.
After the module's KMP migration the tests sat in src/test, which is not
attached to any compilation, so they never ran. Move them into the jvmTest
source set (they rely on JUnit4 @get:Rule helpers, so commonTest isn't an
option) and point the test dependencies at jvmTest.
- New NavigationStateBridge.kt — a single object owning the whole Activity↔BackstackController seam: the intent escape
handoff, the process-death snapshot (NavStateSnapshot + registry key + save-provider), and one restoreAndPersist(...) holding
the full seeding precedence top-to-bottom.
- Deleted RestoredBackstackTransfer.kt (folded in).
- MainActivity shrank — the restoration ladder, NavStateSnapshot, the registry key, savedStateConfiguration, and 9 unused
imports are gone, replaced by attaching the two task lambdas plus one NavigationStateBridge.restoreAndPersist(...) call.

Behavior is unchanged — same precedence, same config-change no-op, same save provider.
Store the mutableState in our AppScoped BackstackController ourselves
Consider trying out AppState later when perhaps it does more things out
of the box for us
Auth↔root reconciliation is an Activity-startup/lifecycle concern, not a
UI one: reconcile() only reads injected singletons and gates the splash
via isReady, and observeForcedLogout() carries its own repeatOnLifecycle
gate. Hosting them in HedvigApp's LaunchedEffects meant reconcile() could
not start until the first composition pass, holding the splash longer
than necessary.

Move both onto lifecycleScope in onCreate, right after the back stack is
seeded, sequenced so the start scene resolves before the forced-logout
watcher maintains it. HedvigApp no longer needs the SessionReconciler
parameter and goes back to being purely about rendering.
This avoids us forgetting to do this and either silently dropping the
pop and having to manually do it on each call site
…backstack-without-appstate

Remove AppState
…on-cleanup

Spike/appstate navigation cleanup
…feature-api-modules

Spike/navigate through feature api modules
…senters

Drive presenter navigation directly via injected Backstack
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.

2 participants