Skip to content

Reply: Migrate from Navigation 2 to Navigation 3#1645

Open
stevenelliottjr wants to merge 2 commits intoandroid:mainfrom
stevenelliottjr:reply-nav3-migration
Open

Reply: Migrate from Navigation 2 to Navigation 3#1645
stevenelliottjr wants to merge 2 commits intoandroid:mainfrom
stevenelliottjr:reply-nav3-migration

Conversation

@stevenelliottjr
Copy link

Summary

  • Replaces NavHost/NavController with Nav3's NavDisplay and a developer-owned SnapshotStateList backstack
  • Replaces NavDestination-based route matching (hasRoute) with direct Route equality checks on type-safe NavKey types
  • Deletes ReplyNavigationActions class — navigation is now simple list operations on the backstack

Changes

File What changed
libs.versions.toml Added navigation3-runtime, navigation3-ui, lifecycle-viewmodel-navigation3
build.gradle.kts Swapped navigation-compose for Nav3 deps
ReplyNavigationActions.kt Route now extends NavKey, deleted ReplyNavigationActions class
ReplyNavigationComponents.kt NavDestination?Route?, hasRoute()== comparison
ReplyApp.kt NavHostNavDisplay with entryProvider lambda, rememberNavController()mutableStateListOf<Route>()

Test plan

  • ./gradlew :Reply:app:assembleDebug builds successfully
  • App launches to Inbox screen
  • Bottom nav: tap each tab (Inbox, Articles, DM, Groups) — correct selection state
  • Expanded screen: NavigationRail shows correct selection state
  • Wide screen: Permanent drawer shows correct selection state
  • System back from non-Inbox tab returns to Inbox
  • Modal drawer opens/closes correctly on rail layout

Fixes #1632

Replace NavHost/NavController with NavDisplay and a developer-owned
SnapshotStateList backstack. NavDestination-based route matching is
replaced with direct Route equality checks on type-safe NavKey types.

Fixes android#1632
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @stevenelliottjr, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the application's navigation architecture by transitioning to the Navigation3 library. The core change involves replacing the existing NavHost and NavController with NavDisplay and a custom, explicit backstack managed by a SnapshotStateList. This migration aims to simplify navigation logic, improve type safety with direct route comparisons, and provide developers with more granular control over the navigation stack, ultimately leading to a more maintainable and predictable user experience.

Highlights

  • Navigation Library Migration: The application's navigation system has been migrated from the traditional AndroidX Navigation-Compose library to the newer Navigation3 library, replacing NavHost and NavController with NavDisplay.
  • Custom Backstack Management: Navigation now utilizes a developer-owned SnapshotStateList to manage the backstack, providing more explicit control over navigation flow.
  • Type-Safe Route Matching: Route matching has been updated from NavDestination-based hasRoute checks to direct equality comparisons on type-safe NavKey types, enhancing robustness and clarity.
  • Simplified Navigation Actions: The ReplyNavigationActions class has been removed, as navigation operations are now handled through simple list manipulations on the backstack, streamlining the navigation API.
  • Dependency Updates: Project dependencies in build.gradle.kts and libs.versions.toml have been updated to include Navigation3 components and remove the older Navigation-Compose library.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • Reply/app/build.gradle.kts
    • Replaced androidx.navigation.compose dependency with androidx.navigation3.runtime, androidx.navigation3.ui, and androidx.lifecycle.viewmodel.navigation3.
  • Reply/app/src/main/java/com/example/reply/ui/ReplyApp.kt
    • Removed imports related to androidx.navigation.compose and ReplyNavigationActions.
    • Added imports for mutableStateListOf, NavEntry, NavDisplay, and Navigation3 decorators.
    • Replaced rememberNavController() with remember { mutableStateListOf<Route>(Route.Inbox) } to manage the navigation backstack.
    • Implemented a custom navigateToTopLevelDestination lambda for backstack manipulation.
    • Replaced the ReplyNavHost composable with NavDisplay, configuring it with the new backstack and entryProvider.
  • Reply/app/src/main/java/com/example/reply/ui/navigation/ReplyNavigationActions.kt
    • Removed imports for NavGraph.Companion.findStartDestination and NavHostController.
    • Added import for NavKey.
    • Modified sealed interface Route to implement NavKey.
    • Deleted the ReplyNavigationActions class.
  • Reply/app/src/main/java/com/example/reply/ui/navigation/ReplyNavigationComponents.kt
    • Removed imports for androidx.navigation.NavDestination and NavDestination.Companion.hasRoute.
    • Updated currentDestination: NavDestination? parameters to currentRoute: Route? in various navigation composables (e.g., ReplyNavigationWrapper, ReplyNavigationRail).
    • Changed navigation item selection logic from currentDestination.hasRoute(replyDestination) to currentRoute == replyDestination.route.
    • Removed the NavDestination?.hasRoute extension function.
  • Reply/gradle/libs.versions.toml
    • Added version definitions for androidx-navigation3 and androidx-lifecycle-viewmodel-navigation3.
    • Added library definitions for androidx-navigation3-runtime, androidx-navigation3-ui, and androidx-lifecycle-viewmodel-navigation3.
Activity
  • No human activity (comments, reviews, or progress updates) has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request successfully migrates the app from Jetpack Navigation 2 to Navigation 3, simplifying the navigation logic by replacing NavController and NavHost with a developer-owned SnapshotStateList for the backstack and a NavDisplay. The code is cleaner and the removal of ReplyNavigationActions is a good simplification. However, I've identified a critical issue in the new implementation within ReplyApp.kt where the navigation state (backStack) is not correctly hoisted. This breaks the existing mechanism for navigating to detail screens, as the callbacks for navigation are unable to modify the backstack.

val navController = rememberNavController()
val navigationActions = remember(navController) {
ReplyNavigationActions(navController)
val backStack = remember { mutableStateListOf<Route>(Route.Inbox) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The backStack is created and managed locally within ReplyApp, but the functions for navigating to and from detail screens (navigateToDetail, closeDetailScreen) are passed in from the caller. This prevents the caller from modifying the backStack to add or remove detail screen routes, which appears to be a regression from the previous navigation implementation.

To fix this, the backStack state should be hoisted. ReplyApp should receive the backStack as a parameter, allowing the caller to create, own, and modify it. This will re-enable navigation to detail screens.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The detail screen navigation in Reply is entirely ViewModel-driven — `navigateToDetail` calls `viewModel.setOpenedEmail()` and `closeDetailScreen` calls `viewModel.closeDetailScreen()`, both of which mutate `ReplyHomeUIState` state. The detail view is rendered inline within `ReplyInboxScreen` based on `replyHomeUIState.isDetailOnlyOpen`, not as a separate navigation destination. This was also the case with Nav2 — the `NavHost` only had four top-level `composable<Route.*>` entries and no detail route.

So there's no regression here — the callers never modified the navigation backstack for detail screens, they only modify ViewModel state.


val navigateToTopLevelDestination: (ReplyTopLevelDestination) -> Unit = { destination ->
if (backStack.lastOrNull() != destination.route) {
while (backStack.size > 1) backStack.removeLast()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using a while loop to remove elements from the list is a bit imperative and can be less performant than bulk operations for large lists. A more declarative and efficient approach is to use subList().clear() to remove a range of elements.

This also makes the intent of clearing the stack down to the root more explicit.

Suggested change
while (backStack.size > 1) backStack.removeLast()
if (backStack.size > 1) backStack.subList(1, backStack.size).clear()

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied — using subList(1, size).clear() now. Thanks.

Replace while loop with subList(1, size).clear() for cleaner intent
and better performance on larger stacks.
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.

[FR]: migrate navigation-compose to Jetpack Navigation 3

1 participant