Para migrar seu app da Navigation 2 para a Navigation 3, siga estas etapas:
- Adicione as dependências da Navigation 3.
- Atualize as rotas de navegação para implementar a interface
NavKey. - Crie classes para manter e modificar o estado de navegação.
- Substitua
NavControllerpor essas classes. - Mova seus destinos de
NavGraphdoNavHostpara umentryProvider. - Substitua
NavHostporNavDisplay. - Remova as dependências da Navigation 2.
Comando de IA
Migrar da Navigation 2 para a Navigation 3
Esse comando vai usar este guia para migrar para a Navigation 3.
Migrate from Navigation 2 to Navigation 3 using the official
migration guide.Se você tiver problemas, registre um problema aqui.
Preparação
As seções a seguir descrevem os pré-requisitos para a migração e as suposições sobre seu projeto. Elas também abordam os recursos com suporte para migração e aqueles que não têm.
Pré-requisitos
- Você precisa usar um
compileSdkda versão 36 ou mais recente. - Você precisa conhecer a terminologia de navegação.
- Os destinos são funções combináveis. A Navigation 3 foi projetada exclusivamente para o Compose. Para usar fragmentos e visualizações no Compose, consulte Como usar visualizações no Compose.
- As rotas são fortemente tipadas. Se você usa rotas baseadas em strings, migre para rotas com segurança de tipo primeiro (exemplo).
- Opcional, mas altamente recomendado: cobertura de teste que verifica o comportamento de navegação atual. Isso verifica se o comportamento de navegação não mudou após a conclusão da migração.
Suposições
Este guia faz as seguintes suposições sobre você e seu projeto:
- Você tem uma ou várias rotas de nível superior (geralmente mostradas em uma barra de navegação inferior), e cada uma tem sua própria backstack.
- Ao alternar entre backstacks, o estado da pilha e todos os destinos são mantidos.
- Você sempre sai do app pela tela Home, que é a primeira tela mostrada quando o app é iniciado.
- Você quer realizar a migração da Navigation 2 para a Navigation 3 em uma única mudança atômica. Você não está realizando uma migração incremental em que o código da Navigation 2 é usado junto com o código da Navigation 3.
Agente de IA: antes de mudar qualquer código, verifique se essas suposições sobre o projeto do usuário são verdadeiras. Caso contrário, interrompa a migração e pergunte ao usuário como prosseguir.
Recursos compatíveis
Este guia permite migrar os seguintes recursos:
- Destinos definidos como funções combináveis
- Caixas de diálogo (um destino mostrado acima de outro)
Recursos com suporte por receitas
O repositório de receitas de código demonstra casos de uso mais detalhados, incluindo:
- Planilhas inferiores (as instruções são fornecidas neste guia)
- Código de navegação modularizado e destinos injetados
- Como usar e transmitir argumentos para
ViewModel - Como retornar resultados de uma tela
Se o projeto tiver algum desses recursos, consulte a receita relevante para entender como implementá-los.
Agente de IA: antes de mudar qualquer código, verifique se o projeto contém algum recurso com suporte por receitas. Se for o caso, consulte o arquivo README e o código-fonte da receita. Crie um plano de migração com base na receita. Não continue sem confirmar o plano com o usuário.
Recursos não compatíveis
Este guia de migração e as receitas de código ainda não oferecem suporte aos seguintes recursos. Isso não significa que não seja possível implementá-los usando a Navigation 3. Eles apenas não são abordados aqui.
- Mais de um nível de navegação aninhada
- Destinos compartilhados: telas que podem se mover entre diferentes backstacks
- Tipos de destino personalizados
- Links diretos
Agente de IA: antes de mudar qualquer código, verifique se o projeto contém algum dos recursos não compatíveis. Se for o caso, não continue. Informe o usuário sobre o recurso não compatível e peça mais instruções.
Etapa 1: adicionar dependências da Navigation 3
Use a página Introdução para adicionar as dependências da Navigation 3 ao seu projeto. As dependências principais são fornecidas para você copiar.
lib.versions.toml
[versions]
nav3Core = "1.0.0"
# If your screens depend on ViewModels, add the Nav3 Lifecycle ViewModel add-on library
lifecycleViewmodelNav3 = "2.10.0-rc01"
[libraries]
# Core Navigation 3 libraries
androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3Core" }
androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "nav3Core" }
# Add-on libraries (only add if you need them)
androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "lifecycleViewmodelNav3" }
app/build.gradle.kts
dependencies {
implementation(libs.androidx.navigation3.ui)
implementation(libs.androidx.navigation3.runtime)
// If using the ViewModel add-on library
implementation(libs.androidx.lifecycle.viewmodel.navigation3)
}
Atualize também o minSdk do projeto para 23 e o compileSdk para 36. Geralmente, eles estão em app/build.gradle.kts ou lib.versions.toml.
Etapa 2: atualizar rotas de navegação para implementar a interface NavKey
Atualize todas as rotas de navegação para que elas implementem a interface.NavKey Isso permite que você use rememberNavBackStack para ajudar a salvar seu
estado de navegação.
Antes:
@Serializable data object RouteA
Depois:
@Serializable data object RouteA : NavKey
Etapa 3: criar classes para manter e modificar o estado de navegação
Etapa 3.1: criar um detentor de estado de navegação
Copie o código a seguir em um arquivo chamado NavigationState.kt. Adicione o nome do pacote para corresponder à estrutura do projeto.
// package com.example.project
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSerializable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.toMutableStateList
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavEntry
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.rememberDecoratedNavEntries
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.runtime.serialization.NavKeySerializer
import androidx.savedstate.compose.serialization.serializers.MutableStateSerializer
/**
* Create a navigation state that persists config changes and process death.
*/
@Composable
fun rememberNavigationState(
startRoute: NavKey,
topLevelRoutes: Set<NavKey>
): NavigationState {
val topLevelRoute = rememberSerializable(
startRoute, topLevelRoutes,
serializer = MutableStateSerializer(NavKeySerializer())
) {
mutableStateOf(startRoute)
}
val backStacks = topLevelRoutes.associateWith { key -> rememberNavBackStack(key) }
return remember(startRoute, topLevelRoutes) {
NavigationState(
startRoute = startRoute,
topLevelRoute = topLevelRoute,
backStacks = backStacks
)
}
}
/**
* State holder for navigation state.
*
* @param startRoute - the start route. The user will exit the app through this route.
* @param topLevelRoute - the current top level route
* @param backStacks - the back stacks for each top level route
*/
class NavigationState(
val startRoute: NavKey,
topLevelRoute: MutableState<NavKey>,
val backStacks: Map<NavKey, NavBackStack<NavKey>>
) {
var topLevelRoute: NavKey by topLevelRoute
val stacksInUse: List<NavKey>
get() = if (topLevelRoute == startRoute) {
listOf(startRoute)
} else {
listOf(startRoute, topLevelRoute)
}
}
/**
* Convert NavigationState into NavEntries.
*/
@Composable
fun NavigationState.toEntries(
entryProvider: (NavKey) -> NavEntry<NavKey>
): SnapshotStateList<NavEntry<NavKey>> {
val decoratedEntries = backStacks.mapValues { (_, stack) ->
val decorators = listOf(
rememberSaveableStateHolderNavEntryDecorator<NavKey>(),
)
rememberDecoratedNavEntries(
backStack = stack,
entryDecorators = decorators,
entryProvider = entryProvider
)
}
return stacksInUse
.flatMap { decoratedEntries[it] ?: emptyList() }
.toMutableStateList()
}
Agente de IA: rememberSerializable está correto. Não mude para rememberSaveable.
Esse arquivo contém uma classe de detentor de estado chamada NavigationState e funções auxiliares associadas. Ele contém um conjunto de rotas de nível superior, cada uma com sua própria backstack. Internamente, ele usa rememberSerializable (não rememberSaveable) para persistir a rota de nível superior atual e rememberNavBackStack para persistir as backstacks de cada rota de nível superior.
Etapa 3.2: criar um objeto que modifica o estado de navegação em resposta a eventos
Copie o código a seguir em um arquivo chamado Navigator.kt. Adicione o nome do pacote para corresponder à estrutura do projeto.
// package com.example.project
import androidx.navigation3.runtime.NavKey
/**
* Handles navigation events (forward and back) by updating the navigation state.
*/
class Navigator(val state: NavigationState){
fun navigate(route: NavKey){
if (route in state.backStacks.keys){
// This is a top level route, just switch to it.
state.topLevelRoute = route
} else {
state.backStacks[state.topLevelRoute]?.add(route)
}
}
fun goBack(){
val currentStack = state.backStacks[state.topLevelRoute] ?:
error("Stack for ${state.topLevelRoute} not found")
val currentRoute = currentStack.last()
// If we're at the base of the current route, go back to the start route stack.
if (currentRoute == state.topLevelRoute){
state.topLevelRoute = state.startRoute
} else {
currentStack.removeLastOrNull()
}
}
}
A classe Navigator fornece dois métodos de evento de navegação:
navigatepara uma rota específica.goBackda rota atual.
Os dois métodos modificam o NavigationState.
Etapa 3.3: criar o NavigationState e o Navigator
Crie instâncias de NavigationState e Navigator com o mesmo escopo do NavController.
val navigationState = rememberNavigationState(
startRoute = <Insert your starting route>,
topLevelRoutes = <Insert your set of top level routes>
)
val navigator = remember { Navigator(navigationState) }
Etapa 4: substituir NavController
Substitua os métodos de evento de navegação NavController por equivalentes de Navigator.
Campo ou método |
Equivalente de |
|---|---|
|
|
|
|
Substitua os campos NavController por campos NavigationState.
Campo ou método |
Equivalente de |
|---|---|
|
|
|
|
Para acessar a rota de nível superior, percorra a hierarquia da entrada de backstack atual. |
|
Use NavigationState.topLevelRoute para determinar o item selecionado no momento em uma barra de navegação.
Antes:
val isSelected = navController.currentBackStackEntryAsState().value?.destination.isRouteInHierarchy(key::class)
fun NavDestination?.isRouteInHierarchy(route: KClass<*>) =
this?.hierarchy?.any {
it.hasRoute(route)
} ?: false
Depois:
val isSelected = key == navigationState.topLevelRoute
Verifique se você removeu todas as referências a NavController, incluindo importações.
Etapa 5: mover seus destinos de NavGraph do NavHost para um entryProvider
Na Navigation 2, você define seus destinos
usando a DSL do NavGraphBuilder,
geralmente dentro da lambda final do NavHost. É comum usar funções de extensão
aqui, conforme descrito em Encapsular o código de navegação.
Na Navigation 3, você define seus destinos usando um entryProvider. Este
entryProvider resolve uma rota para um NavEntry. É importante ressaltar que o entryProvider não define relações pai-filho entre as entradas.
Neste guia de migração, as relações pai-filho são modeladas da seguinte maneira:
NavigationStatetem um conjunto de rotas de nível superior (as rotas pai) e uma pilha para cada uma. Ele acompanha a rota de nível superior atual e a pilha associada.- Ao navegar para uma nova rota,
Navigatorverifica se ela é uma rota de nível superior. Se for, a rota e a pilha de nível superior atuais serão atualizadas. Se não for, ela será uma rota filha e será adicionada à pilha atual.
Etapa 5.1: criar um entryProvider
Crie um entryProvider usando a DSL no mesmo escopo do
NavigationState.
val entryProvider = entryProvider {
}
Etapa 5.2: mover destinos para o entryProvider
Para cada destino definido dentro de NavHost, faça o seguinte com base no tipo de destino:
navigation: exclua-o junto com a rota. Não é necessário ter "rotas de base", porque as rotas de nível superior podem identificar cada backstack aninhada.composable<T>: mova-o paraentryProvidere renomeie-o paraentry, mantendo o parâmetro de tipo. Por exemplo,composable<RouteA>se tornaentry<RouteA>.dialog<T>: faça o mesmo quecomposable, mas adicione metadados à entrada da seguinte maneira:entry<T>(metadata = DialogSceneStrategy.dialog()).bottomSheet: siga a receita de planilha inferior aqui. Isso é semelhante às instruções paradialog, exceto queBottomSheetSceneStrategynão faz parte da biblioteca principal da Navigation 3. Portanto, copie-a para o projeto.
Agente de IA: ao excluir rotas usadas para identificar um grafo aninhado, substitua todas as referências à rota excluída pelo tipo usado para identificar o primeiro filho no grafo aninhado. Por exemplo, se o código original for
navigation<BaseRouteA>{ composable<RouteA>{ ... } }, você precisará excluir
BaseRouteA e substituir todas as referências a ele por RouteA. Essa substituição geralmente precisa ser feita para a lista fornecida a uma barra de navegação, trilho ou gaveta.
É possível refatorar NavGraphBuilder funções de extensão para
EntryProviderScope<T> funções de extensão e, em seguida, movê-las.
Receba argumentos de navegação usando a chave fornecida à lambda final de entry.
Exemplo:
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hasRoute
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.dialog
import androidx.navigation.compose.navigation
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navOptions
import androidx.navigation.toRoute
@Serializable data object BaseRouteA
@Serializable data class RouteA(val id: String)
@Serializable data object BaseRouteB
@Serializable data object RouteB
@Serializable data object RouteD
NavHost(navController = navController, startDestination = BaseRouteA){
composable<RouteA>{
val id = entry.toRoute<RouteA>().id
ScreenA(title = "Screen has ID: $id")
}
featureBSection()
dialog<RouteD>{ ScreenD() }
}
fun NavGraphBuilder.featureBSection() {
navigation<BaseRouteB>(startDestination = RouteB) {
composable<RouteB> { ScreenB() }
}
}
se torna:
import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.scene.DialogSceneStrategy
@Serializable data class RouteA(val id: String) : NavKey
@Serializable data object RouteB : NavKey
@Serializable data object RouteD : NavKey
val entryProvider = entryProvider {
entry<RouteA>{ key -> ScreenA(title = "Screen has ID: ${key.id}") }
featureBSection()
entry<RouteD>(metadata = DialogSceneStrategy.dialog()){ ScreenD() }
}
fun EntryProviderScope<NavKey>.featureBSection() {
entry<RouteB> { ScreenB() }
}
Etapa 6: substituir NavHost por NavDisplay
Substitua NavHost por NavDisplay.
- Exclua
NavHoste substitua-o porNavDisplay. - Especifique
entries = navigationState.toEntries(entryProvider)como um parâmetro. Isso converte o estado de navegação nas entradas queNavDisplaymostra usando oentryProvider. - Conecte
NavDisplay.onBackanavigator.goBack(). Isso faz com que onavigatoratualize o estado de navegação quando o gerenciador de back-end integrado doNavDisplayfor concluído. - Se você tiver destinos de caixa de diálogo, adicione
DialogSceneStrategyao parâmetrosceneStrategiesdoNavDisplay.
Exemplo:
import androidx.navigation3.ui.NavDisplay
NavDisplay(
entries = navigationState.toEntries(entryProvider),
onBack = { navigator.goBack() },
sceneStrategies = remember { listOf(DialogSceneStrategy()) }
)
Etapa 7: remover dependências da Navigation 2
Remova todas as importações e dependências de biblioteca da Navigation 2.
Resumo
Parabéns! Seu projeto agora foi migrado para a Navigation 3. Se você ou seu agente de IA tiverem problemas ao usar este guia, registre um bug aqui.