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 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 um mix de conteúdo do Compose e da View, mas todos os destinos de navegação precisam ser combináveis para ativar a migração do Compose de navegação. Até lá, continue usando o componente de navegação baseado em fragmentos na sua base de código de interoperabilidade de visualizações e 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 fragmentos, desde que mantenha fragmentos para hospedar seu conteúdo combinável.

Etapas da migração

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

Se o app já estiver seguindo um padrão de design UDF e nosso guia de arquitetura, a migração para o Jetpack Compose e o Navigation Compose não vai exigir refatorações importantes de outras camadas do app, além da camada de 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 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. Crie tipos para cada destino de navegação. Use 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 referenciar tenham acesso a ele (geralmente dentro do elemento combinável 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()
        // ...
    }

  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 foi migrada anteriormente 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 projetar a interface do Compose, especificamente como ViewModels e eventos de navegação precisam ser transmitidos para combináveis, a próxima etapa é mudar a forma como você fornece 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 = {},
    ) {
        // ...
    }

  8. Substitua todas as chamadas de navegação findNavController() pelas navController e transmita-as como eventos de navegação para cada tela combinável, em vez de transmitir o navController inteiro. Essa abordagem segue as práticas recomendadas para expor eventos de funções combináveis aos autores de chamada 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. Ele pode ser recebido diretamente na entrada da backstack no destino ou de 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"))
                    }
                )
            }
            composable<Second> { backStackEntry ->
                val secondRoute = backStackEntry.toRoute<Second>()
                SecondScreen(
                    id = secondRoute.id,
                    onIconClick = {
                        // findNavController().navigate(secondScreenToThirdScreenAction)
                        navController.navigate(Third)
                    }
                )
            }
            // ...
        }
    }

  9. 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.

Confira as mesmas etapas com mais detalhes relacionados ao Navigation Compose na documentação de configuração.

Casos de uso comuns

Não importa qual componente de navegação você está 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 Navegar com o Compose.

Extrair 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. É necessário armazenar objetos complexos como dados em uma única fonte da verdade, como a camada de dados. Para mais informações, consulte Como recuperar dados complexos durante a navegação.

Se os fragmentos estiverem passando objetos complexos como argumentos, refatore o código primeiro, de modo a permitir o armazenamento e a busca desses objetos na camada de dados. Confira exemplos no repositório do Now in Android.

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 você ainda usa fragmentos como destinos no código. Para começar a usar o Navigation Compose, todos os destinos precisam ser combináveis. É possível acompanhar essa solicitação de recurso no Issue Tracker.

Animações de transição

A partir da Navigation 2.7.0-alpha01, o suporte para definir transições personalizadas, anteriormente de AnimatedNavHost, agora é oferecido diretamente no 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 de 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 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: uma postagem do blog que documenta a jornada de migração do app de exemplo Sunflower das visualizações para o Compose, que também inclui a migração para o Navigation Compose.
  • Jetnews para cada tela: uma postagem de blog que documenta a refatoração e migração do exemplo do Jetnews para oferecer suporte a todas as telas com o Jetpack Compose e o Navigation Compose.