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 da navegação do Jetpack.

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

Pré-requisitos de migração

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

O uso do Navigation Compose em um app somente do Compose não é um pré-requisito. Você pode continuar usando componentes de navegação baseados em fragmentos, desde que mantenha fragmentos para hospedar seu conteúdo combinável.

Etapas da migração

Esteja você seguindo nossa estratégia de migração recomendada ou adotando outra abordagem, chegará a um ponto em que todos os destinos de navegação são elementos combináveis de tela, com os fragmentos atuando apenas como contêineres combináveis. Nesta etapa, é possível migrar para o Navigation Compose.

Caso seu app já esteja seguindo 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, além da camada da interface.

Para migrar para o Navigation Compose, siga estas etapas:

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

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

  3. Configure o NavController em um local em que todos os elementos combináveis que precisam referenciar a ele tenham acesso a ele. Isso geralmente fica dentro do elemento App. Essa abordagem segue os princípios de elevação de estado e permite que você use o NavController como a fonte da verdade para navegar entre telas combináveis e manter a backstack:

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

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

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

  5. Adicione os destinos composable para criar seu gráfico de navegação. Se cada tela já tiver sido migrada para o Compose, esta 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(/* ... */)
            }
            // ...
        }
    }

  6. Se você seguiu as orientações sobre como arquitetar a interface do Compose, especificamente como ViewModels e 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 tela combinável. Muitas vezes, você pode usar a injeção do Hilt e o ponto de integração com o Compose e o Navigation via hiltViewModel:

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

  7. 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 exposição de eventos de funções combináveis aos autores de chamada e mantém o navController como a única fonte da verdade.

    1. Se você já usou o plug-in Safe Args para gerar rotas e ações de navegação, substitua-o por uma rota, um caminho de string exclusivo para cada destino.
    2. Para substituir o Safe Args ao transmitir dados, consulte Navegar com argumentos.
    3. Para segurança de tipos no Navigation Compose, leia a seção Safe Args abaixo.

      @Composable
      fun SampleNavHost(
          navController: NavHostController
      ) {
          NavHost(navController = navController, startDestination = "first") {
              composable("first") {
                  FirstScreen(
                      onButtonClick = {
                          // findNavController().navigate(firstScreenToSecondScreenAction)
                          navController.navigate("second_screen_route")
                      }
                  )
              }
              composable("second") {
                  SecondScreen(
                      onIconClick = {
                          // findNavController().navigate(secondScreenToThirdScreenAction)
                          navController.navigate("third_screen_route")
                      }
                  )
              }
              // ...
          }
      }

  8. Remova todos os fragmentos, layouts XML relevantes, navegação e outros recursos desnecessários e dependências desatualizadas de fragmentos e navegação do Jetpack.

As mesmas etapas podem ser encontradas em mais detalhes relacionados ao Navigation Compose na documentação de configuração.

Casos de uso comuns

Independentemente do componente de navegação que você esteja usando, os mesmos princípios de navegação se aplicam.

Confira alguns casos de uso comuns da migração:

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

Safe Args

Ao contrário do Jetpack Navigation, o Navigation Compose não oferece suporte ao uso do plug-in Safe Args para gerar código. Em vez disso, você pode conseguir segurança de tipos com o Navigation Compose estruturando seu código para torná-lo seguro durante a execução.

Recuperar dados complexos durante a navegação

O Navigation Compose é baseado em rotas de string e, ao contrário da navegação do Jetpack, não tem suporte à transmissão de Parcelables e serials personalizados como argumentos.

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

Se os fragmentos estiverem passando objetos complexos como argumentos, refatore o código primeiro, de modo que permita armazenar e buscar esses objetos na camada de dados. Consulte o repositório Now in Android para ver exemplos.

Limitações

Esta seção descreve 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 ainda usa fragmentos como destinos no código. Para começar a usar o Navigation Compose, todos os destinos precisam ser elementos combináveis. Acompanhe essa solicitação de recurso no Issue Tracker.

Animações de transição

A partir do Navigation 2.7.0-alpha01, a compatibilidade com a definição de transições personalizadas, anteriormente de AnimatedNavHost, agora tem suporte direto em 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 Now in Android: um app Android totalmente funcional criado totalmente com Kotlin e 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: uma postagem do blog que documenta a jornada de migração do app de exemplo Sunflower do Views para o Compose, que também inclui a migração para o Navigation Compose.
  • Jetnews para todas as telas: 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.