Generated banner for Navigation 3
Generated logo for Navigation 3

Navigation 3

Android (Google)OfficialCreated by Google LLCDistributed by Android (Google)

  • Skill
  • Tool integration
  • Software development
  • Apache-2.0
  • No issues found

From the author

Learn how to install and migrate to Jetpack Navigation 3, and how to implement features and patterns such as deep links, multiple backstacks, scenes (dialogs, bottom sheets, list-detail, two-pane, supporting pane), conditional navigation (such as logged-in navigation versus anonymous), returning results from flows, integration with Hilt, ViewModel, Kotlin, and view interoperability.
— the description by Google LLC

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides developers with the necessary documentation, migration steps, and code recipes to adopt Jetpack Navigation 3. It covers the core concepts of the library, including back stack management, adaptive layouts using Scenes, deep link handling, and integration with Hilt, ViewModel, and Material 3 Adaptive components. The resource is designed to help developers transition from Navigation 2 or implement new navigation flows with a focus on type-safe, Compose-first architecture.

What it does

  • Provides a step-by-step migration path from Navigation 2 to Navigation 3.
  • Explains how to define destinations, manage back stacks, and use NavDisplay.
  • Offers code recipes for common patterns like bottom sheets, dialogs, and adaptive layouts.
  • Demonstrates how to handle deep links, animations, and conditional navigation.
  • Shows integration with dependency injection frameworks like Hilt and Koin.
  • Details how to pass arguments to ViewModels and return results between screens.

When to use it

  • When you need to migrate an existing Android app from Navigation 2 to Navigation 3.
  • When building a new Android application using Jetpack Compose.
  • When implementing complex navigation patterns like multiple back stacks or adaptive list-detail views.
  • When you need to handle deep links or conditional navigation flows in a type-safe manner.

Advantages

  • Full control over the back stack and navigation state.
  • Native support for adaptive layouts and multi-pane displays.
  • Strongly typed navigation routes.
  • Simplified integration with Jetpack Compose and ViewModel.
  • Extensive library of code recipes for common UI and architectural patterns.

Usage examples

“How do I migrate my existing Android app from Navigation 2 to Navigation 3?”
The agent will guide you through the migration steps, including updating dependencies, replacing NavController with the new state management classes, and switching from NavHost to NavDisplay.
“Show me how to implement a list-detail layout using Navigation 3.”
The agent will provide the relevant code recipe for implementing adaptive list-detail scenes using the Navigation 3 Scenes API.
“How can I handle conditional navigation for an authentication flow?”
The agent will explain how to switch navigation flows based on conditions, such as checking user login status, using the conditional navigation recipe.
“How do I pass arguments to a ViewModel in Navigation 3?”
The agent will demonstrate how to pass navigation arguments to a ViewModel constructed using the viewModel() function.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/android/navigation-3/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip navigation-3.zip -d .agents/skills

Documentation by Google LLC


Migration guide

  • Navigation 2 to Navigation 3 migration guide: Step-by-step guide to migrate an Android application from Navigation 2 to Navigation 3, covering dependency updates, route changes, state management, and UI component replacements.

Requirements

Developer documentation

  • Navigation 3. Search documentation for more information on basics, saving and managing navigation state, modularizing navigation code, creating custom layouts using Scenes, animating between destinations, or applying logic or wrappers to destinations.

Recipes

Code examples showcasing common patterns.

Basic API usage
Common UI
  • Common UI: Demonstrates how to implement a common navigation UI pattern with a bottom navigation bar and multiple back stacks, where each tab in the navigation bar has its own navigation history.

Scenes
Use built-in Scenes
  • Dialog: Shows how to create a Dialog.

Create custom Scenes
  • BottomSheet: Shows how to create a BottomSheet destination.

  • List-Detail Scene: Demonstrates how to implement adaptive list-detail layouts using the Navigation 3 Scenes API.

  • Two pane Scene: Demonstrates how to implement adaptive two-pane layouts using the Navigation 3 Scenes API.

Material Adaptive
  • Material List-Detail: Demonstrates how to implement an adaptive list-detail layout using Material 3 Adaptive.

  • Material Supporting Pane: Demonstrates how to implement an adaptive supporting pane layout using Material 3 Adaptive.

Animations
  • Animations: Shows how to override the default animations for all destinations and a single destination.

  • Conditional Transitions: Shows how to implement conditional transition animations.

Common back stack behavior
  • Multiple back stacks: Shows how to create multiple top level routes, each with its own back stack. Top level routes are displayed in a navigation bar allowing users to switch between them. State is retained for each top level route, and the navigation state persists config changes and process death.

Conditional navigation
  • Conditional navigation: Switch to a different navigation flow when a condition is met. For example, for authentication or first-time user onboarding.

Lifecycle
Architecture
Working with ViewModel
Passing navigation arguments
  • Basic ViewModel : Navigation arguments are passed to a ViewModel constructed using viewModel()

Returning results

Files

references/android/guide/navigation/navigation-3/recipes/passingarguments.md

Passing Arguments to ViewModels (Hilt)

This recipe demonstrates how to pass navigation arguments (keys) to a ViewModel using Hilt for dependency injection.

How it works

This example uses Dagger/Hilt's assisted injection feature:

  1. The ViewModel is annotated with @HiltViewModel and its constructor uses @AssistedInject to receive the navigation key (which is annotated with @Assisted).

  2. An @AssistedFactory interface is defined to create the ViewModel.

  3. The hiltViewModel composable function is used to obtain the ViewModel instance. A creationCallback is provided to pass the navigation key to the factory, making it available to the ViewModel.

Note : The rememberViewModelStoreNavEntryDecorator is added to the NavDisplay's entryDecorators. This ensures that ViewModels are correctly scoped to their corresponding NavEntry, so that a new ViewModel instance is created for each unique navigation key. Image: untitled Explore View the full recipe on GitHub.

/*
 * Copyright 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.nav3recipes.passingarguments.viewmodels.hilt

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.compose.dropUnlessResumed
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.ui.NavDisplay
import com.example.nav3recipes.content.ContentBlue
import com.example.nav3recipes.content.ContentGreen
import com.example.nav3recipes.passingarguments.viewmodels.basic.RouteB
import com.example.nav3recipes.ui.setEdgeToEdgeConfig
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.lifecycle.HiltViewModel

data object RouteA
data class RouteB(val id: String)

@AndroidEntryPoint
class HiltViewModelsActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        setEdgeToEdgeConfig()
        super.onCreate(savedInstanceState)
        setContent {
            val backStack = remember { mutableStateListOf<Any>(RouteA) }

            NavDisplay(
                backStack = backStack,
                onBack = { backStack.removeLastOrNull() },

                // In order to add the `ViewModelStoreNavEntryDecorator` (see comment below for why)
                // we also need to add the default `NavEntryDecorator`s as well. These provide
                // extra information to the entry's content to enable it to display correctly
                // and save its state.
                entryDecorators = listOf(
                    rememberSaveableStateHolderNavEntryDecorator(),
                    rememberViewModelStoreNavEntryDecorator()
                ),
                entryProvider = entryProvider {
                    entry<RouteA> {
                        ContentGreen("Welcome to Nav3") {
                            LazyColumn {
                                items(10) { i ->
                                    Button(onClick = dropUnlessResumed {
                                        backStack.add(RouteB("$i"))
                                    }) {
                                        Text("$i")
                                    }
                                }
                            }
                        }
                    }
                    entry<RouteB> { key ->
                        val viewModel = hiltViewModel<RouteBViewModel, RouteBViewModel.Factory>(
                            // Note: We need a new ViewModel for every new RouteB instance. Usually
                            // we would need to supply a `key` String that is unique to the
                            // instance, however, the ViewModelStoreNavEntryDecorator (supplied
                            // above) does this for us, using `NavEntry.contentKey` to uniquely
                            // identify the viewModel.
                            //
                            // tl;dr: Make sure you use rememberViewModelStoreNavEntryDecorator()
                            // if you want a new ViewModel for each new navigation key instance.
                            creationCallback = { factory ->
                                factory.create(key)
                            }
                        )
                        ScreenB(viewModel = viewModel)
                    }
                }
            )
        }
    }
}

@Composable
fun ScreenB(viewModel: RouteBViewModel) {
    ContentBlue("Route id: ${viewModel.navKey.id} ")
}

@HiltViewModel(assistedFactory = RouteBViewModel.Factory::class)
class RouteBViewModel @AssistedInject constructor(
    @Assisted val navKey: RouteB
) : ViewModel() {

    @AssistedFactory
    interface Factory {
        fun create(navKey: RouteB): RouteBViewModel
    }
}

Passing Arguments to ViewModels (Basic)

This recipe demonstrates how to pass navigation arguments (keys) to a ViewModel using a custom ViewModelProvider.Factory.

How it works

  1. A custom ViewModelProvider.Factory is created that takes the navigation key as a constructor parameter.

  2. Inside the entry composable, viewModel(factory = ...) is used to create the ViewModel instance, passing the current navigation key to the factory. This makes the navigation key available to the ViewModel.

Note : The rememberViewModelStoreNavEntryDecorator is added to the NavDisplay's entryDecorators. This ensures that ViewModels are correctly scoped to their corresponding NavEntry, so that a new ViewModel instance is created for each unique navigation key. Image: untitled Explore View the full recipe on GitHub.

/*
 * Copyright 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.nav3recipes.passingarguments.viewmodels.basic

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.compose.dropUnlessResumed
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.ui.NavDisplay
import com.example.nav3recipes.content.ContentBlue
import com.example.nav3recipes.content.ContentGreen
import com.example.nav3recipes.ui.setEdgeToEdgeConfig

data object RouteA

data class RouteB(val id: String)

class BasicViewModelsActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        setEdgeToEdgeConfig()
        super.onCreate(savedInstanceState)
        setContent {
            val backStack = remember { mutableStateListOf<Any>(RouteA) }

            NavDisplay(
                backStack = backStack,
                onBack = { backStack.removeLastOrNull() },
                // In order to add the `ViewModelStoreNavEntryDecorator` (see comment below for why)
                // we also need to add the default `NavEntryDecorator`s as well. These provide
                // extra information to the entry's content to enable it to display correctly
                // and save its state.
                entryDecorators = listOf(
                    rememberSaveableStateHolderNavEntryDecorator(),
                    rememberViewModelStoreNavEntryDecorator()
                ),
                entryProvider = entryProvider {
                    entry<RouteA> {
                        ContentGreen("Welcome to Nav3") {
                            LazyColumn {
                                items(10) { i ->
                                    Button(onClick = dropUnlessResumed {
                                        backStack.add(RouteB("$i"))
                                    }) {
                                        Text("$i")
                                    }
                                }
                            }
                        }
                    }
                    entry<RouteB> { key ->
                        // Note: We need a new ViewModel for every new RouteB instance. Usually
                        // we would need to supply a `key` String that is unique to the
                        // instance, however, the ViewModelStoreNavEntryDecorator (supplied
                        // above) does this for us, using `NavEntry.contentKey` to uniquely
                        // identify the viewModel.
                        //
                        // tl;dr: Make sure you use rememberViewModelStoreNavEntryDecorator()
                        // if you want a new ViewModel for each new navigation key instance.
                        ScreenB(viewModel = viewModel(factory = RouteBViewModel.Factory(key)))
                    }
                }
            )
        }
    }
}

@Composable
fun ScreenB(viewModel: RouteBViewModel = viewModel()) {
    ContentBlue("Route id: ${viewModel.key.id} ")
}

class RouteBViewModel(
    val key: RouteB
) : ViewModel() {
    class Factory(
        private val key: RouteB,
    ) : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return RouteBViewModel(key) as T
        }
    }
}

Passing Arguments to ViewModels (Koin)

This recipe demonstrates how to pass navigation arguments (keys) to a ViewModel using Koin for dependency injection.

How it works

  1. A Koin module is defined that provides the ViewModel.

  2. The koinViewModel composable function is used to get the ViewModel instance.

  3. The navigation key is passed to the ViewModel's constructor using parametersOf(key). This makes the navigation key available to the ViewModel.

Note : The rememberViewModelStoreNavEntryDecorator is added to the NavDisplay's entryDecorators. This ensures that ViewModels are correctly scoped to their corresponding NavEntry, so that a new ViewModel instance is created for each unique navigation key. Image: untitled Explore View the full recipe on GitHub.

package com.example.nav3recipes.passingarguments.viewmodels.koin

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.lifecycle.ViewModel
import androidx.lifecycle.compose.dropUnlessResumed
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.ui.NavDisplay
import com.example.nav3recipes.content.ContentBlue
import com.example.nav3recipes.content.ContentGreen
import com.example.nav3recipes.ui.setEdgeToEdgeConfig
import org.koin.compose.KoinApplication
import org.koin.compose.viewmodel.koinViewModel
import org.koin.core.module.dsl.viewModelOf
import org.koin.core.parameter.parametersOf
import org.koin.dsl.koinConfiguration
import org.koin.dsl.module

data object RouteA
data class RouteB(val id: String)

class KoinViewModelsActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {

        setEdgeToEdgeConfig()
        super.onCreate(savedInstanceState)
        setContent {
            val backStack = remember { mutableStateListOf<Any>(RouteA) }

            // Koin Compose Entry point
            KoinApplication(
                configuration = koinConfiguration {
                    modules(appModule)
                }
            ) {
                NavDisplay(
                    backStack = backStack,
                    onBack = { backStack.removeLastOrNull() },

                    // In order to add the `ViewModelStoreNavEntryDecorator` (see comment below for why)
                    // we also need to add the default `NavEntryDecorator`s as well. These provide
                    // extra information to the entry's content to enable it to display correctly
                    // and save its state.
                    entryDecorators = listOf(
                        rememberSaveableStateHolderNavEntryDecorator(),
                        rememberViewModelStoreNavEntryDecorator()
                    ),
                    entryProvider = entryProvider {
                        entry<RouteA> {
                            ContentGreen("Welcome to Nav3") {
                                LazyColumn {
                                    items(10) { i ->
                                        Button(onClick = dropUnlessResumed {
                                            backStack.add(RouteB("$i"))
                                        }) {
                                            Text("$i")
                                        }
                                    }
                                }
                            }
                        }
                        entry<RouteB> { key ->
                            val viewModel = koinViewModel<RouteBViewModel> {
                                parametersOf(key)
                            }
                            ScreenB(viewModel = viewModel)
                        }
                    }
                )
            }
        }
    }
}

// Local Koin Module
private val appModule = module {
    viewModelOf(::RouteBViewModel)
}

@Composable
fun ScreenB(viewModel: RouteBViewModel) {
    ContentBlue("Route id: ${viewModel.navKey.id} ")
}

class RouteBViewModel(val navKey: RouteB) : ViewModel()

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Google LLC
Publisher
Android (Google) (Official)
Distributed by
Android (Google)
Source
android/skills · navigation/navigation-3 · commit bf68e2d
Licence
Apache License 2.0 (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE.txt)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Google LLC or Android (Google).

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download navigation-3.zip