Migrar a navegação do Jetpack para o Navigation Compose

A API Navigation Compose permite navegar entre elementos combináveis em um app do Compose, aproveitando o componente, a infraestrutura e os recursos do Jetpack Navigation.

Esta página descreve como migrar de uma navegação do Jetpack baseada em fragmentos para a Navigation Compose, como parte da migração maior da interface baseada em visualização para o Jetpack Compose.

Pré-requisitos de migração

Você pode migrar para o Navigation Compose quando conseguir substituir todos os fragmentos por elementos combináveis de tela correspondentes. Os elementos combináveis de tela podem conter uma mistura de conteúdo do Compose e da visualização, mas todos os destinos de navegação precisam ser elementos combináveis para permitir a migração do Navigation Compose. Até lá, continue usando o componente de navegação baseado em fragmentos na base de código de interoperabilidade de visualização e do Compose. Consulte a documentação de interoperabilidade de navegação para mais informações.

Etapas da migração

Seja seguindo nossa estratégia de migração recomendada ou adotando outra abordagem, você vai chegar a um ponto em que todos os destinos de navegação serão funções de composição de tela, com fragmentos atuando apenas como contêineres de composição. Nesta fase, você pode migrar para o Navigation Compose.

Se o app já segue um padrão de design de UDF e nosso guia de arquitetura, a migração para o Jetpack Compose e o Navigation Compose não vai exigir grandes refatorações de outras camadas do app, exceto a de interface.

Para migrar para o Navigation Compose, siga estas etapas:

  1. Adicione a dependência do Navigation Compose ao app.
  2. Crie um elemento combinável App-level e adicione-o à Activity como o ponto de entrada do Compose, substituindo a configuração do layout da visualização:

    class SampleActivity : ComponentActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            // setContentView<ActivitySampleBinding>(this, R.layout.activity_sample)
            setContent {
                SampleApp(/* ... */)
            }
        }
    }

  3. Crie tipos para cada destino de navegação. Use um data object para destinos que não exigem dados e data class ou class para destinos que exigem dados.

    @Serializable data object First
    @Serializable data class Second(val id: String)
    @Serializable data object Third
    

  4. Configure o NavController em um local em que todos os elementos combináveis que precisam fazer referência a ele tenham acesso (geralmente, dentro do elemento combinável App). Essa abordagem segue os princípios da elevação de estado e permite usar o NavController como a fonte da verdade para navegar entre telas combináveis e manter a backstack:

    @Composable
    fun SampleApp() {
        val navController = rememberNavController()
        // ...
    }

  5. Crie o NavHost do app dentro do elemento combinável App e transmita o navController:

    @Composable
    fun SampleApp() {
        val navController = rememberNavController()
    
        SampleNavHost(navController = navController)
    }
    
    @Composable
    fun SampleNavHost(
        navController: NavHostController
    ) {
        NavHost(navController = navController, startDestination = First) {
            // ...
        }
    }

  6. Adicione os destinos composable para criar o gráfico de navegação. Se cada tela já tiver sido migrada para o Compose, essa etapa consiste apenas em extrair esses elementos combináveis de tela dos fragmentos para os destinos composable:

    class FirstFragment : Fragment() {
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View {
            return ComposeView(requireContext()).apply {
                setContent {
                    // FirstScreen(...) EXTRACT FROM HERE
                }
            }
        }
    }
    
    @Composable
    fun SampleNavHost(
        navController: NavHostController
    ) {
        NavHost(navController = navController, startDestination = First) {
            composable<First> {
                FirstScreen(/* ... */) // EXTRACT TO HERE
            }
            composable<Second> {
                SecondScreen(/* ... */)
            }
            // ...
        }
    }

  7. Se você seguiu as orientações sobre como arquitetar sua interface do Compose, especificamente como os ViewModels e os eventos de navegação precisam ser transmitidos para elementos combináveis, a próxima etapa é mudar a forma como você fornece o ViewModel para cada elemento combinável de tela. Muitas vezes, é possível usar a injeção do Hilt e o ponto de integração com o Compose e a navegação usando hiltViewModel:

    @Composable
    fun FirstScreen(
        // viewModel: FirstViewModel = viewModel(),
        viewModel: FirstViewModel = hiltViewModel(),
        onButtonClick: () -> Unit = {},
    ) {
        // ...
    }

  8. Substitua todas as chamadas de navegação findNavController() pelas navController e as transmita como eventos de navegação para cada tela combinável, em vez de transmitir toda a navController. Essa abordagem segue as práticas recomendadas de expor eventos de funções combináveis para autores de chamadas e mantém o navController como a única fonte de verdade.

    Os dados podem ser transmitidos para um destino criando uma instância da classe de rota definida para esse destino. Em seguida, ele pode ser acessado diretamente na entrada da backstack no destino ou em um ViewModel usando SavedStateHandle.toRoute().

    @Composable
    fun SampleNavHost(
        navController: NavHostController
    ) {
        NavHost(navController = navController, startDestination = First) {
            composable<First> {
                FirstScreen(
                    onButtonClick = {
                        // findNavController().navigate(firstScreenToSecondScreenAction)
                        navController.navigate(Second(id = "ABC"))
                    }
                )
            }
            <compos>ableSecond { backSt>ackEntry -
                val secondRoute = backStackEnt<ry.toR>outeSecond()
                SecondScreen(
                    id = secondRoute.id,
                    onIconClick = {
                        // findNavController().navigate(secondScreenToThirdScreenAction)
                        navController.navigate(Third)
                    }
                )
            }
            // .nippets.kt

  9. Remova todos os fragmentos, layouts XML relevantes, navegação desnecessária e outros recursos, além de dependências desatualizadas de fragmentos e do Jetpack Navigation.

As mesmas etapas com mais detalhes relacionados ao Navigation Compose estão disponíveis na documentação de configuração.

Casos de uso comuns

Não importa qual componente de navegação você esteja usando, os mesmos princípios de navegação se aplicam.

Os casos de uso comuns ao migrar incluem:

Para mais informações sobre esses casos de uso, consulte Como navegar com o Compose.

Como recuperar dados complexos durante a navegação

É altamente recomendável não transmitir objetos de dados complexos ao navegar. Em vez disso, transmita as informações mínimas necessárias, como um identificador exclusivo ou outra forma de ID, como argumentos ao executar ações de navegação. Armazene objetos complexos como dados em uma única fonte de verdade, como a camada de dados. Para mais informações, consulte Como recuperar dados complexos durante a navegação.

Se os fragmentos estiverem transmitindo objetos complexos como argumentos, refatore o código primeiro, de modo que seja possível armazenar e buscar esses objetos na camada de dados. Confira exemplos no repositório do Now in Android.

Limitações

Nesta seção, descrevemos as limitações atuais do Navigation Compose.

Migração incremental para o Navigation Compose

No momento, não é possível usar o Navigation Compose enquanto você ainda usa fragmentos como destinos no código. Para começar a usar o Navigation Compose, todos os destinos precisam ser combináveis. Acompanhe essa solicitação de recurso no Issue Tracker.

Animações de transição

A partir do Navigation 2.7.0-alpha01, o suporte para definir transições personalizadas, que antes era do AnimatedNavHost, agora é diretamente compatível com o NavHost. Leia as notas da versão para mais informações.

Saiba mais

Para mais informações sobre como migrar para o Navigation Compose, consulte os seguintes recursos:

  • Codelab do Navigation Compose: aprenda os conceitos básicos do Navigation Compose com um codelab prático.
  • Repositório do Now in Android: um app Android totalmente funcional criado com o Kotlin e o Jetpack Compose, que segue as práticas recomendadas de design e desenvolvimento do Android e inclui o Navigation Compose.
  • Como migrar o Sunflower para o Jetpack Compose (link em inglês): uma postagem do blog que documenta a jornada de migração do app de exemplo Sunflower de visualizações para o Compose, que também inclui a migração para o Navigation Compose.
  • Jetnews para cada tela (link em inglês): uma postagem do blog que documenta a refatoração e a migração do exemplo do Jetnews para oferecer suporte a todas as telas com o Jetpack Compose e o Navigation Compose.