Migrar para a Navigation 3

A navegação 3 representa uma mudança fundamental na forma como o Jetpack Compose processa o estado de navegação e oferece vantagens arquitetônicas significativas em relação à navegação 2.

Entenda as mudanças arquitetônicas e as etapas necessárias para migrar um app do Wear Compose da navegação 2 para a navegação 3.

Principais vantagens da navegação 3

  • Controle direto da pilha de retorno: a NavBackStack é fundamentalmente apenas uma lista mutável de objetos NavKey, que representa o histórico de telas que o usuário visitou. Você a controla exatamente como faria com qualquer Kotlin MutableList (add, removeLast, clear). Você manipula diretamente a lista para realizar ações de navegação, como adicionar uma chave para avançar ou remover uma chave para voltar.
  • Design do Compose em primeiro lugar: a backstack é modelada como um estado observável padrão. A modificação do histórico de navegação se comporta exatamente como a atualização de qualquer outro estado do Compose, acionando automaticamente a recomposição para mostrar a tela atual.
  • Segurança de tipos por padrão: as rotas baseadas em strings são eliminadas completamente. A navegação usa objetos de dados e classes de dados serializáveis.
  • Apresentações desacopladas (estratégias de cena): a camada de transição da interface (NavDisplay e SwipeDismissableSceneStrategy) é totalmente separada do rastreamento de estado (NavBackStack), permitindo uma integração mais simples das transições de navegação integradas do Wear OS.

Etapas da migração

1. Atualizar dependências

Remova a dependência antiga androidx.wear.compose:compose-navigation e introduza as novas dependências divididas da navegação 3, além do suporte à serialização do Kotlin.

Remova:

implementation("androidx.wear.compose:compose-navigation:...")

Adicionar:

implementation("androidx.navigation3:navigation3-runtime:...") // State logic
implementation("androidx.navigation3:navigation3-ui:...")      // Display logic
implementation("androidx.wear.compose:compose-navigation3:...") // Wear gestures
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:...") // Requires compiler plugin

2. Atualizar destinos para implementar NavKey

Na navegação 2, você pode ter usado strings ou objetos genéricos para o roteamento. Na navegação 3, é necessário implementar a interface de marcador NavKey e anotar cada objeto de tela com @Serializable.

Por que isso é necessário? Para que a backstack possa ser salva e restaurada durante o encerramento do processo, o navigation3-runtime subjacente depende de kotlinx-serialization para serializar o estado.

Antes (navegação 2: rotas genéricas com segurança de tipos):

sealed class Nav2Screen {
    data object Landing : Nav2Screen()
    data object List : Nav2Screen()
}

Depois (navegação 3: NavKey + serializável):

@Serializable
sealed interface MigrationScreen : NavKey {
    @Serializable
    data object Landing : MigrationScreen

    @Serializable
    data object List : MigrationScreen
}

3. Substituir a lógica de roteamento (NavController para NavBackStack)

Substitua o NavController por uma NavBackStack inicializada usando rememberNavBackStack. Você também precisa instanciar o SwipeDismissableSceneStrategy especificamente para o Wear OS.

Antes (navegação 2):

val navController = rememberSwipeDismissableNavController()

Depois (navegação 3):

val backStack = rememberNavBackStack(MigrationScreen.Landing as NavKey)
val strategy = rememberSwipeDismissableSceneStrategy<NavKey>()

4. Substituir NavHost por NavDisplay e a DSL entryProvider

O contêiner NavHost e a DSL do builder interno composable("route") { ... } são substituídos por NavDisplay e a DSL entryProvider { entry<Key> { ... } }.

Antes (navegação 2):

SwipeDismissableNavHost(navController = navController, startDestination = "menu") {
    composable("menu") {
        GreetingScreen(
            onShowList = { navController.navigate("list") }
        )
    }
    composable("list") {
        ListScreen()
    }
}

Depois (navegação 3):

NavDisplay(
    backStack = backStack,
    sceneStrategies = listOf(strategy),
    entryProvider = entryProvider {
        entry<MigrationScreen.Landing> {
            GreetingScreen(
                onShowList = { backStack.add(MigrationScreen.List) }
            )
        }
        entry<MigrationScreen.List> {
            ListScreen()
        }
    }
)