Skip to content

feat: migrate navigation to Navigation 3 [MOB-20] - #5187

Open
Garzas wants to merge 10 commits into
developfrom
feat/navigation3-migration
Open

feat: migrate navigation to Navigation 3 [MOB-20]#5187
Garzas wants to merge 10 commits into
developfrom
feat/navigation3-migration

Conversation

@Garzas

@Garzas Garzas commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

https://wearezeta.atlassian.net/browse/MOB-20


PR Submission Checklist for internal contributors

  • The PR Title

    • conforms to the style of semantic commits messages¹ supported in Wire's Github Workflow²
    • contains a reference JIRA issue number like SQPIT-764
    • answers the question: If merged, this PR will: ... ³
  • The PR Description

    • is free of optional paragraphs and you have filled the relevant parts to the best of your ability

Summary

This PR completes the Android navigation migration from Compose Destinations to Navigation 3.

It replaces generated destinations, navigation graphs and direct NavHostController access with:

  • serializable, feature-owned typed routes;
  • one WireNavigationController mutation boundary;
  • feature-owned wireEntry providers;
  • explicit Navigation 3 ViewModel ownership;
  • route-based Metro graph resolution;
  • typed navigation results;
  • deterministic session and account lifecycle handling;
  • a significantly reduced platform-host responsibility in WireActivity.

Why

The previous navigation integration mixed several independent responsibilities:

  • destination identity;
  • back-stack mutation;
  • current account observation;
  • Metro graph selection;
  • ViewModel ownership;
  • Android lifecycle and Activity effects.

In particular, existing screens could indirectly depend on transient currentSession values or parent/previous NavBackStackEntry lookups. During login, logout and account switching, this made navigation and ViewModel lifetime difficult to reason about.

The new architecture does not guarantee that currentSession will never temporarily become null. It guarantees that this temporary state cannot change the Metro graph, ViewModel owner or ViewModel of an existing navigation entry.

Core identity model

For a route such as:

ConversationRoute(
    sessionId = sessionA,
    conversationId = conversationId,
    entryId = entryX,
)

the responsibilities are separated as follows:

sessionId = A
    → selects the Metro dependency graph for account A

entryId = X
    → identifies one concrete Navigation 3 entry
    → selects the default Entry(X) ViewModelStoreOwner

conversationId
    → becomes an explicit assisted ViewModel argument

The final ViewModel is constructed from:

Metro factory from graph A
        +
ViewModelStore owned by Entry(X)
        +
typed conversation arguments

Metro graph identity and ViewModel ownership are deliberately independent.

Architecture

Typed routes

Generated destination classes and route-string classification are replaced by serializable feature-owned routes:

@Serializable
data class DeviceDetailsRoute(
    override val sessionId: WireSessionId,
    val targetUserId: DeviceTargetUserId,
    val clientId: String,
    override val entryId: WireNavEntryId = WireNavEntryId.random(),
) : SessionRoute

AuthenticationRoute and SessionRoute explicitly determine which Metro graph is required.

Single back-stack mutation boundary

All back-stack changes go through:

WireNavigationController

Navigation decisions are represented as data:

WireNavigationCommand(
    destination = HomeRoute(sessionId),
    backStackMode = WireBackStackMode.CLEAR_WHOLE,
)

Features no longer receive a mutable NavHostController.

Explicit ViewModel ownership

Navigation 3 provides an entry-specific ViewModelStoreOwner. Wire additionally supports explicitly shared owners:

  • Entry(entryId) — one concrete screen occurrence; default;
  • Flow(flowId) — a multi-screen flow such as login or registration;
  • Session(sessionId) — deliberately shared account-session state;
  • Application — process-wide state.

Making a shared owner available does not change the default owner. A ViewModel uses a shared owner only when requested explicitly.

Route-based Metro graph resolution

MetroWireEntryEnvironment resolves the dependency graph from the typed route:

AuthenticationRoute → authentication graph
SessionRoute(A)     → retained session graph A

The resolver does not use currentSession to select a graph for an existing route.

The resulting MetroViewModelFactory is provided at the entry boundary, while Navigation 3 independently provides the ViewModelStoreOwner.

Explicit ViewModel arguments

Generated SavedStateHandle.navArgs() usage is replaced by:

route.toViewModelArgs()

and focused assisted factories:

class DeviceDetailsViewModel @AssistedInject constructor(
    @Assisted args: DeviceDetailsViewModelArgs,
    ...
) : ViewModel() {

    @AssistedFactory
    interface Factory {
        fun create(args: DeviceDetailsViewModelArgs): DeviceDetailsViewModel
    }
}

SavedStateHandle remains valid for restorable UI state, but it is no longer the source of navigation identity.

Session graph lifecycle

Session graphs now follow an explicit lifecycle:

ACTIVE
  → INVALIDATING
  → REMOVED
  → AVAILABLE for a new generation

An invalidating or removed graph cannot be silently recreated by a stale navigation entry.

Logout and account switching coordinate cleanup in a deterministic order:

  1. stop admitting new work for the old graph;
  2. switch or resolve the surviving account;
  3. remove outgoing navigation entries and owners;
  4. dispose the old Metro session graph;
  5. delete/logout the old session.

Typed results

Generated ResultRecipient and ResultBackNavigator APIs are replaced by typed result contracts addressed to the requesting entryId.

The result registry supports:

  • typed values;
  • explicit cancellation;
  • saved-state restoration;
  • result pruning after non-linear stack replacement.

WireActivity as a platform host

WireActivity remains responsible for Android-specific integration:

  • Android lifecycle;
  • intents and deep links;
  • Activity results;
  • permissions and dialogs;
  • root Compose hosting;
  • external Activity and custom-tab effects.

Navigation decisions, session policy, graph lifecycle and back-stack operations are delegated to focused, testable collaborators.

Main implementation areas

  • core/navigation-kmp

    • typed routes;
    • navigation commands;
    • back-stack reducer;
    • owner identities;
    • typed results and deep links.
  • core/navigation

    • WireNav3Host;
    • entry providers;
    • entry/shared ViewModel owner decorators;
    • responsive presentation and transition policies.
  • core/di

    • Metro ViewModel gateway;
    • assisted ViewModel creation;
    • ownership diagnostics.
  • app/navigation/runtime

    • Metro entry environment;
    • retained session graph lifecycle;
    • Activity, session and intent coordinators;
    • production contribution catalog.
  • Feature navigation files

    • typed routes;
    • wireEntry providers;
    • semantic action interfaces;
    • explicit ViewModel argument mapping.

Migration scope

The branch migrates the complete production navigation surface, including:

  • authentication and registration;
  • session-backed device authentication;
  • home and top-level destinations;
  • conversations and conversation details;
  • media, gallery and typed results;
  • new conversation and channels;
  • settings and device management;
  • user profiles and team migration;
  • app lock;
  • Cells;
  • Meetings;
  • Sketch;
  • Android Activity effects and deep links.

The production contribution catalog currently assembles 19 entry installers and validates unique route/result registrations.

Legacy Compose Destinations navigation infrastructure and generated navigation wrappers are removed as part of the complete cutover.

KMP boundary

This PR contains core/navigation-kmp, which holds the platform-independent contracts required by Navigation 3:

  • WireRoute;
  • WireNavigationCommand;
  • WireNavigationController;
  • WireViewModelOwner;
  • result and deep-link contracts.

It does not contain the later secret-project UI or feature KMP migrations. Those changes remain outside this branch.

Kalium/toolchain dependency

This branch points to the companion Kalium branch:

chore/navigation3-agp-9.2

The Kalium change only aligns the Android build toolchain:

  • AGP 9.2;
  • compile SDK 37;
  • Gradle 9.4.1.

This alignment is required by the Navigation 3/Lifecycle 2.11 dependency chain. It does not contain a functional Kalium or KMP migration.

Required regression testing

The following flows should receive particular attention:

Authentication

  • fresh login;
  • registration;
  • second-account login;
  • Too Many Devices cancellation;
  • returning from session-backed authentication;
  • Initial Sync and Setting Up Wire transitions.

Multi-account

  • add a second account;
  • switch between accounts;
  • switch while one session is temporarily unavailable;
  • logout active account;
  • logout inactive account;
  • notifications from an inactive account;
  • process recreation with multiple accounts.

Navigation

  • opening and closing conversations;
  • system back from Settings, Archive, Drive and Cells;
  • deep links into session destinations;
  • dialogs and tablet presentation;
  • navigation results from media, profile and conversation details;
  • transitions and interaction gating during exit animations.

Lifecycle

  • no ViewModel recreation during ordinary recomposition;
  • entry ViewModel cleared after the entry is removed;
  • flow ViewModel retained until the last flow entry is removed;
  • outgoing session graph not recreated during logout;
  • temporary currentSession == null does not change the graph or owner of an existing entry.

Review guide

A useful review order is:

  1. WireRoute and WireViewModelOwner;
  2. WireNavigationController and back-stack reducer;
  3. wireEntry and WireNav3Host;
  4. WireViewModelStoreNavEntryDecorator;
  5. MetroWireEntryEnvironment;
  6. SessionGraphStoreViewModel;
  7. one vertical example such as ConversationRoute;
  8. authentication/account-switch coordinators;
  9. WireActivity production host;
  10. legacy navigation cleanup.

Risks and trade-offs

  • The complete migration touches a large number of screens and ViewModels.
  • Navigation contracts and lifecycle responsibilities are now explicit, which adds infrastructure code.
  • Feature entry providers contain more visible wiring than generated Compose Destinations code.
  • Session and owner lifecycle correctness now depends on following the documented typed APIs.
  • Full device regression and playtesting remain required before considering the migration production-ready.

The additional code represents previously implicit behavior that is now explicit, testable and independently owned: route identity, stack operations, Metro graph selection, ViewModel lifetime, result delivery and session teardown.

@Garzas Garzas self-assigned this Aug 18, 2026
@Garzas
Garzas requested a review from a team as a code owner August 18, 2026 05:50
@Garzas
Garzas requested review from MohamadJaara, ohassine, saleniuk and valerio-bettini and removed request for a team August 18, 2026 05:50
@AndroidBob

Copy link
Copy Markdown
Collaborator

New ADR(s) in this PR 📚:

@Garzas
Garzas requested a review from yamilmedina August 18, 2026 05:50
@Garzas
Garzas requested a review from sbakhtiarov August 18, 2026 05:53
@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Test Results

2 586 tests   2 586 ✅  1m 30s ⏱️
  332 suites      0 💤
  332 files        0 ❌

Results for commit a59a707.

♻️ This comment has been updated with latest results.

@codecov

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 39.10256% with 665 lines in your changes missing coverage. Please review.
✅ Project coverage is 52.41%. Comparing base (ca4f7ca) to head (a59a707).
⚠️ Report is 1 commits behind head on develop.

Files with missing lines Patch % Lines
...com/wire/android/ui/WireActivityNavigation3Host.kt 4.27% 112 Missing ⚠️
...n/com/wire/android/ui/home/HomeNavigation3Entry.kt 28.33% 86 Missing ⚠️
...conversations/ConversationAuxNavigation3Mappers.kt 0.00% 51 Missing ⚠️
...android/ui/WireActivityNavigation3DialogActions.kt 0.00% 47 Missing ⚠️
...ntication/SessionAuthenticationViewModelFactory.kt 0.00% 44 Missing ⚠️
...conversations/ConversationAuxNavigation3Entries.kt 16.98% 44 Missing ⚠️
.../wire/android/ui/WireActivityNavigation3Effects.kt 70.07% 26 Missing and 15 partials ⚠️
...i/home/conversations/ConversationAuxNavigation3.kt 69.14% 17 Missing and 12 partials ⚠️
...me/conversations/ConversationNavigation3Entries.kt 14.70% 29 Missing ⚠️
...me/conversations/ConversationNavigation3Mappers.kt 0.00% 25 Missing ⚠️
... and 27 more

❌ Your patch check has failed because the patch coverage (39.10%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #5187      +/-   ##
===========================================
- Coverage    52.41%   52.41%   -0.01%     
===========================================
  Files          667      732      +65     
  Lines        23846    25494    +1648     
  Branches      3932     4176     +244     
===========================================
+ Hits         12499    13362     +863     
- Misses       10186    10851     +665     
- Partials      1161     1281     +120     
Files with missing lines Coverage Δ
...n/com/wire/android/feature/AccountSwitchUseCase.kt 70.37% <ø> (+4.85%) ⬆️
...rc/main/kotlin/com/wire/android/ui/WireActivity.kt 80.00% <ø> (+17.00%) ⬆️
.../ui/authentication/AuthenticationViewModelGraph.kt 0.00% <ø> (ø)
...on/create/details/CreateAccountDetailsViewModel.kt 86.84% <100.00%> (ø)
...cation/create/email/CreateAccountEmailViewModel.kt 44.89% <100.00%> (ø)
...on/create/summary/CreateAccountSummaryViewModel.kt 0.00% <ø> (ø)
...roid/ui/authentication/welcome/WelcomeViewModel.kt 63.15% <ø> (-1.85%) ⬇️
...m/wire/android/ui/calling/CallActivityViewModel.kt 94.44% <100.00%> (-1.02%) ⬇️
...om/wire/android/ui/calling/StartingCallActivity.kt 45.45% <ø> (ø)
.../android/ui/calling/ongoing/OngoingCallActivity.kt 83.33% <ø> (ø)
... and 126 more

... and 53 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ca4f7ca...a59a707. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Garzas added 4 commits August 18, 2026 10:11
…igration

# Conflicts:
#	app/src/main/kotlin/com/wire/android/di/AppModule.kt
#	app/src/main/kotlin/com/wire/android/di/metro/WireMetroViewModelBindings.kt
#	app/src/main/kotlin/com/wire/android/ui/debug/DebugScreen.kt
#	app/stability/app-devDebug.stability
@github-actions

Copy link
Copy Markdown
Contributor

APKs built during tests are available here. Scroll down to Artifacts!

@github-actions

Copy link
Copy Markdown
Contributor

APKs built during tests are available here. Scroll down to Artifacts!

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