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 enquanto aproveita 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 o 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 depois de substituir todos os seus fragmentos pelos elementos combináveis de tela correspondentes. Os elementos combináveis de tela podem conter uma combinação de conteúdos do Compose e da visualização, mas todos os destinos de navegação precisam ser elementos combináveis para ativar a migração da navegação do Compose. Até lá, continue usando o componente de navegação baseado em fragmento 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 exclusivo do Compose não é um pré-requisito. Você pode continuar usando o componente de navegação baseado em fragmento, desde que mantenha fragmentos para hospedar seu conteúdo combinável.

Etapas da migração

Não importa se você está seguindo nossa estratégia de migração recomendada ou outra abordagem, até um ponto em que todos os destinos de navegação são elementos combináveis de tela, os fragmentos atuam apenas como contêineres combináveis. Nesta fase, é possível migrar para o Navigation Compose.

Se o app já estiver 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 de navegação do Compose ao app.
  2. Crie um elemento combinável App-level e adicione-o à Activity como seu 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. Configure o NavController em um local em que todos os elementos combináveis que precisam fazer referência 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 usar 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 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 o gráfico de navegação. Se cada tela tiver sido migrada anteriormente para o Compose, esta etapa vai consistir 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 as ViewModels e os eventos de navegação precisam ser transmitidos para elementos combináveis, a próxima etapa é mudar a forma de fornecer o ViewModel para cada elemento combinável de tela. 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 todo o navController. Essa abordagem segue as práticas recomendadas de expor eventos de funções combináveis aos autores da chamada e mantém o navController como a única fonte da verdade.

    1. Se você já usou o plug-in Safe Args para gerar direções e ações de navegação, substitua-o por uma route: um caminho de string para o elemento combinável 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 desnecessária e outros recursos e dependências desatualizadas de Fragment e navegação do Jetpack.

Você pode encontrar as mesmas etapas com mais detalhes relacionados ao Navigation Compose na documentação de configuração.

Casos de uso comuns

Seja qual for o componente de navegação usado, os mesmos princípios de navegação se aplicam.

Os casos de uso comuns da migração incluem os seguintes:

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 alcançar a 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 rota de string e, ao contrário do Jetpack Navigation, não oferece suporte à transmissão de Parcelables e serializáveis 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 mais informações, consulte Como recuperar dados complexos ao navegar.

Se seus fragmentos estiverem transmitindo objetos complexos como argumentos, considere refatorar o código primeiro, de uma forma que permita armazenar e buscar esses objetos da 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. É possível acompanhar essa solicitação de recurso no Issue Tracker.

Animações de transição

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

Saiba mais

Para saber mais sobre como migrar para o Navigation Compose, consulte estes recursos:

  • Codelab do Navigation Compose: aprenda os conceitos básicos de navegação do Compose com um codelab prático.
  • Repositório Now in Android: um app Android totalmente funcional criado inteiramente com Kotlin e Jetpack Compose, que segue as práticas recomendadas de design e desenvolvimento do Android e inclui o Navigation Compose.
  • Migração do app Sunflower para o Jetpack Compose (em inglês): uma postagem do blog que registra a jornada de migração do app de exemplo Sunflower das visualizações para o Compose, incluindo a migração para o Navigation Compose.
  • Jetnews para cada tela: 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.