Skip to content

[navigation] Nav3 migration plan: consolidated from spike/nav3-about-runtime-only #1244

Description

@wax911

Navigation 3 Migration Plan

Implementation branch: spike/nav3-about-runtime-only

All features remain runtimeOnly — app module does NOT compile against feature implementations. Navigation 3 v1.1.2, Koin 4.2.1.


PR 1 — Nav3 dependency + app:navigation key contracts

  • Add navigation3-runtime and kotlinx-serialization to app:navigation
  • Add AniTrendNavKey (open interface, non-sealed for cross-package subtyping)
  • Add NavCommand (Push, Pop)
  • Add NavigationDispatcher interface
  • app:navigation stays non-Compose — no compile plugin, no buildFeatures.compose

PR 2 — common:navigation

  • New :common:navigation module — the only Compose-bearing nav contract layer
  • FeatureNavEntryProvider — runtime feature registration seam
  • FeatureNavRegistry + FeatureNavEntryScope — Compose entry registration
  • FeatureNavEntryProviderRepository — abstraction for provider discovery
  • Gets Compose naturally via matchesCommonModule()hasComposeSupport()

PR 3 — app runtime host foundation

  • AniTrendNavigationDispatcher — SharedFlow-based dispatcher impl
  • RuntimeFeatureNavRegistry — adapter: providers → Nav3 entries
  • KoinFeatureNavEntryProviderRepository — Koin getAll via bind
  • Duplicate key detection (warning, not crash — idempotent install)
  • Unit tests: registry registration, duplicate, missing key, multi-provider
  • Process-death verified: kill + relaunch → falls back to startKey

PR 4 — About production migration

  • AboutNavKey data object
  • AboutNavEntryProvider implemented in feature/about
  • Koin binding: factory { AboutNavEntryProvider() } bind FeatureNavEntryProvider::class
  • Nav3AwareProvider interface + startNav3OrActivity extension (tries Nav3 first, falls back to Activity)
  • FeatureProvider accepts nullable NavigationDispatcher via constructor
  • Settings call site updated to startNav3OrActivity
  • Old Activity fallback preservedAboutScreen and AboutRouter.Provider intact
  • Verified: feature/about on runtimeClasspath, NOT compileClasspath

PR 5 — MainActivity Compose shell spike

  • MainComposeShellActivity (debug-only): ModalNavigationDrawer + Scaffold + TopAppBar + AniTrendNav3Host
  • Hamburger icon opens drawer with navigation items
  • Drawer calls dispatcher.navigate() — not state-key changes
  • LegacyFragmentNavKey created for future Fragment compat
  • Does NOT migrate all drawer destinations — shell and content are separate PRs

PR 6+ — feature-by-feature migrations

Each feature follows the same pattern:

  1. Create XxxNavKey in app:navigation
  2. Create XxxNavEntryProvider in the feature module (runtimeOnly)
  3. Koin: factory { XxxNavEntryProvider() } bind FeatureNavEntryProvider::class
  4. Register entry in AniTrendNav3Host entryProvider
  5. Verify runtimeOnly boundary
  6. Verify back navigation
  7. Verify process death
  8. Remove Activity fallback only after stabilization

Completed migrations:

Feature NavKey Notes
Airing AiringNavKey Pure Compose screen
ImageViewer ImageViewerNavKey(imageSources, initialIndex) Serializable payload, download permission via rememberLauncherForActivityResult
Settings SettingsNavKey(destination) + SettingsDestination enum Wraps existing Compose NavHost; internal child navigation preserved

Drawer destination keys created (content pending per-feature migration):

HomeNavKey, DiscoverNavKey, NewsNavKey, SocialNavKey, EpisodesNavKey, ReviewsNavKey, SuggestionsNavKey, AnimeListNavKey, MangaListNavKey, ForumsNavKey


PR 10 — External boundaries

  • DeepLinkMapper: resolves anilist.co URIs and anitrend:// scheme links to Nav3 keys
  • MediaNavKey(mediaId), ProfileNavKey(userId) for content deep links
  • Notification intent → Nav3 key resolution
  • Pattern: Activities stay for system entry points, translate input to Nav3 keys

Module Architecture (final)

app:navigation  → pure contracts, non-Compose
common:navigation → Compose entry contribution (providers, registry, scopes)
app             → runtime host (dispatcher, registry impl, NavDisplay)
feature:*       → runtimeOnly, contribute via FeatureNavEntryProvider + Koin bind

Koin Multi-Provider Pattern

Use org.koin.dsl.bind (NOT org.koin.core.module.dsl.bind):

import org.koin.dsl.bind

module {
    factory { AboutNavEntryProvider() } bind FeatureNavEntryProvider::class
    factory { AiringNavEntryProvider() } bind FeatureNavEntryProvider::class
}

// App: koin.getAll<FeatureNavEntryProvider>()

Hard Rules

  • app:navigation must not depend on Compose
  • common:navigation depends on app:navigation
  • Feature modules depend on app:navigation + common:navigation
  • App must not implementation-depend on feature modules
  • Feature modules remain runtimeOnly from app
  • Nav3 keys are immutable, small, @Serializable, contain no framework objects
  • Duplicate key registration warns, does not crash
  • Keep old Activity/Fragment fallbacks until Nav3 path is process-death-verified

Branch

spike/nav3-about-runtime-only

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions