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 da interface baseada em visualização para o Jetpack Compose.

Pré-requisitos de migração

É possível migrar para o Navigation Compose quando você conseguir substituir todos os fragmentos por elementos combináveis de tela correspondentes. Os elementos combináveis da tela podem conter uma mistura de conteúdo do Compose e da View, 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 sua base de código de interoperabilidade do View e do Compose. Consulte a documentação de interoperabilidade de navegação para mais informações.

Não é necessário usar o Navigation Compose em um app somente com Compose. Você pode continuar usando o componente de navegação baseado em fragmentos, desde que mantenha os fragmentos para hospedar seu conteúdo combinável.

Etapas da migração

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

Se o app já seguir 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 grandes refatorações de outras camadas do app, além da camada de UI.

Para migrar para o Navigation Compose, siga estas etapas:

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

    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 referenciá-lo tenham acesso a ele (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 seu gráfico de navegação. Se cada tela já tiver sido migrada para o Compose, esta etapa consistirá apenas em extrair esses elementos combináveis de tela dos seus 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 arquitetura da sua interface do Compose, especificamente como ViewModels e eventos de navegação devem 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, é possível usar a injeção do Hilt e o ponto de integração dele com o Compose e a 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 todo o navController. Essa abordagem segue as práticas recomendadas de expor eventos de funções combináveis para chamadores 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 ele. Ele pode ser obtido diretamente da 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 desnecessária e outros recursos, além de dependências obsoletas de fragmentos e do Jetpack Navigation.

Encontre 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ê esteja usando, os mesmos princípios de navegação se aplicam.

Confira alguns casos de uso comuns ao migrar:

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

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. Você precisa armazenar 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 forma que permita armazenar e buscar esses objetos da camada de dados. Confira exemplos no repositório 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 e os fragmentos como destinos no seu código. Para começar a usar o Navigation Compose, todos os seus destinos precisam ser combináveis. Você pode acompanhar 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, antes do AnimatedNavHost, agora é compatível diretamente com o NavHost. Leia as notas da versão para mais informações.

Saiba mais

Para mais informações sobre a migração 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 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 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 no blog que documenta a refatoração e migração da amostra do Jetnews para oferecer suporte a todas as telas com Jetpack Compose e Navigation Compose.