Participe do evento ⁠#Android11: apresentação de lançamento da versão Beta no dia 3 de junho.

Migrar para a Navigation component

A Navigation component é uma biblioteca que pode gerenciar navegação complexa, animação de transição, links diretos e argumento verificado em tempo de compilação passando entre as telas do app.

Este documento serve como um guia de propósito geral para migração de um app existente para o uso da Navigation component.

De modo geral, a migração envolve quatro etapas:

  1. Mover a lógica da IU específica da tela para fora das atividades: retire a lógica da IU do app das atividades, garantindo que cada atividade possua apenas a própria lógica dos componentes da IU de navegação global, como um Toolbar, enquanto delega a implementação de cada tela a um fragmento ou destino personalizado.

  2. Integrar a Navigation component: para cada atividade, crie um gráfico de navegação que contenha um ou mais fragmentos gerenciados por essa atividade. Substitua transações de fragmento por operações da Navigation component.

  3. Adicionar destinos de atividades: substitua chamadas startActivity() por ações que usam destinos de atividades.

  4. Combinar atividades: combine gráficos de navegação nos casos em que várias atividades compartilham um mesmo layout.

Pré-requisitos

Este guia presume que você já tenha migrado o app para usar as bibliotecas AndroidX. Caso não tenha feito isso, migre seu projeto para usar o AndroidX antes de continuar.

Remover a lógica da IU específica da tela das atividades

Atividades são componentes em nível de sistema que facilitam uma interação gráfica entre o app e o Android. As atividades são registradas no manifesto do app para que o Android saiba quais delas estão disponíveis para iniciar. A classe da atividade permite que o app também reaja às mudanças no Android, como quando a IU do app está entrando ou saindo do primeiro plano, está em rotação e assim por diante. A atividade também pode servir como um lugar para compartilhar o estado entre telas.

No contexto do app, as atividades precisam servir como um host para navegação e manter a lógica e o conhecimento de como fazer a transição entre telas, transmitir dados e assim por diante. No entanto, é melhor deixar o gerenciamento dos detalhes da IU para uma parte menor e reutilizável. A implementação recomendada para esse padrão é a que usa fragmentos. Veja Atividade única: por que, quando e como para saber mais sobre as vantagens de usar fragmentos (link em inglês). A navegação é compatível com fragmentos por meio da dependência navegação-fragmento. A navegação é também compatível com tipos de destino personalizados.

Se o app não estiver usando fragmentos, a primeira coisa a fazer é migrar cada tela para usar um fragmento. Você não está removendo a atividade neste momento. Em vez disso, está criando um fragmento para representar a tela e separar a lógica da IU por responsabilidade.

Introdução de fragmentos

Para ilustrar o processo de introdução de fragmentos, vamos começar com o exemplo de um app que consiste em duas telas: uma tela de lista de produtos e uma tela de detalhes do produto. Clicar em um produto na tela de lista leva o usuário a uma tela de detalhes para saber mais sobre o produto.

Neste exemplo, as telas de lista e de detalhes são atividades separadas.

Criar um novo layout para hospedar a IU

Para introduzir um fragmento, comece pela criação de um novo arquivo de layout para a atividade para hospedar o fragmento. Isso substitui o layout atual de visualização de conteúdo da atividade.

Para uma visualização simples, você pode usar um FrameLayout, como mostrado no exemplo a seguir product_list_host:

<FrameLayout
       xmlns:app="http://schemas.android.com/apk/res-auto"
       xmlns:android="http://schemas.android.com/apk/res/android"
       android:id="@+id/main_content"
       android:layout_height="match_parent"
       android:layout_width="match_parent" />
    

O atributo id refere-se à seção de conteúdo em que depois adicionamos o fragmento.

Em seguida, na função onCreate() da atividade, modifique a referência do arquivo de layout na função onCreate para apontar para esse novo arquivo de layout:

Kotlin

    class ProductListActivity : AppCompatActivity() {
        ...
        override fun onCreate(savedInstanceState: Bundle?) {
            ...
            // Replace setContentView(R.layout.product_list) with the line below
            setContentView(R.layout.product_list_host)
            ...
        }
    }
    

Java

    public class ProductListActivity extends AppCompatActivity {
        ...
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            ...
            // Replace setContentView(R.layout.product_list); with the line below
            setContentView(R.layout.product_list_host);
            ...
        }
    }
    

O layout existente (product_list, neste exemplo) é usado como a visualização raiz do fragmento que você está prestes a criar.

Criar um fragmento

Crie um novo fragmento para gerenciar a IU da tela. É uma prática recomendada ser consistente com o nome do host da sua atividade. O snippet abaixo usa ProductListFragment, por exemplo:

Kotlin

    class ProductListFragment : Fragment() {
        // Leave empty for now.
    }
    

Java

    public class ProductListFragment extends Fragment {
        // Leave empty for now.
    }
    

Mover a lógica de atividade para um fragmento

Depois de estabelecida a definição do fragmento, a próxima etapa é mover a lógica da IU dessa tela da atividade para esse novo fragmento. Se você estiver vindo de uma arquitetura baseada em atividades, provavelmente tem muita lógica de criação de visualizações acontecendo na função onCreate() da atividade.

Veja um exemplo de tela baseada em atividades com lógica de IU que precisamos mover:

Kotlin

    class ProductListActivity : AppCompatActivity() {

        // Views and/or ViewDataBinding references, Adapters...
        private lateinit var productAdapter: ProductAdapter
        private lateinit var binding: ProductListActivityBinding

        ...

        // ViewModels, System Services, other Dependencies...
        private val viewModel: ProductListViewModel by viewModels()

        ...

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)

            // View initialization logic
            DataBindingUtil.setContentView(this, R.layout.product_list_activity)

            // Post view initialization logic
            // Connect adapters
            productAdapter = ProductAdapter(productClickCallback)
            binding.productsList.setAdapter(productAdapter)

            // Initialize view properties, set click listeners, etc.
            binding.productsSearchBtn.setOnClickListener {...}

            // Subscribe to state
            viewModel.products.observe(this, Observer { myProducts ->
                ...
            })

            // ...and so on
        }
       ...
    }
    

Java

    public class ProductListActivity extends AppCompatActivity {

        // Views and/or ViewDataBinding references, adapters...
        private ProductAdapter productAdapter;
        private ProductListActivityBinding binding;

        ...

        // ViewModels, system services, other dependencies...
        private ProductListViewModel viewModel;

        ...

        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            // View initialization logic
            DataBindingUtil.setContentView(this, R.layout.product_list_activity);

            // Post view initialization logic
            // Connect adapters
            productAdapter = new ProductAdapter(productClickCallback);
            binding.productsList.setAdapter(productAdapter);

            // Initialize ViewModels and other dependencies
            ProductListViewModel viewModel = ViewModelProviders.of(this).get(ProductListViewModel.java);

            // Initialize view properties, set click listeners, etc.
            binding.productsSearchBtn.setOnClickListener(v -> { ... });

            // Subscribe to state
            viewModel.getProducts().observe(this, myProducts ->
                ...
           );

           // ...and so on
       }

    

Sua atividade também pode estar controlando quando e como o usuário navega para a próxima tela, como mostrado no exemplo a seguir:

Kotlin

        // Provided to ProductAdapter in ProductListActivity snippet.
        private val productClickCallback = ProductClickCallback { product ->
            show(product)
        }

        fun show(product: Product) {
            val intent = Intent(this, ProductActivity::class.java)
            intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.id)
            startActivity(intent)
        }
    

Java

    // Provided to ProductAdapter in ProductListActivity snippet.
    private ProductClickCallback productClickCallback = this::show;

    private void show(Product product) {
        Intent intent = new Intent(this, ProductActivity.class);
        intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.getId());
        startActivity(intent);
    }
    

Dentro do seu fragmento, você distribui este trabalho entre onCreateView() e onViewCreated(), com apenas a lógica de navegação restante na atividade:

Kotlin

    class ProductListFragment : Fragment() {

        private lateinit var binding: ProductListFragmentBinding
        private val viewModel: ProductListViewModel by viewModels()

         // View initialization logic
        override fun onCreateView(inflater: LayoutInflater,
                container: ViewGroup?,
                savedInstanceState: Bundle?): View? {
            binding = DataBindingUtil.inflate(
                    inflater,
                    R.layout.product_list,
                    container,
                    false
            )
            binding.root
        }

        // Post view initialization logic
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            // Connect adapters
            productAdapter = ProductAdapter(productClickCallback)
            binding.productsList.setAdapter(productAdapter)

            // Initialize view properties, set click listeners, etc.
            binding.productsSearchBtn.setOnClickListener {...}

            // Subscribe to state
            viewModel.products.observe(this, Observer { myProducts ->
                ...
            })

            // ...and so on
        }

        // Provided to ProductAdapter
        private val productClickCallback = ProductClickCallback { product ->
            if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
                (requireActivity() as ProductListActivity).show(product)
            }
        }
        ...
    }
    

Java

    public class ProductListFragment extends Fragment {

        private ProductAdapter productAdapter;
        private ProductListFragmentBinding binding;

        // View initialization logic
        @Nullable
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater,
                @Nullable ViewGroup container,
                @Nullable Bundle savedInstanceState) {
            binding = DataBindingUtil.inflate(
                    inflater,
                    R.layout.product_list_fragment,
                    container,
                    false);
            return binding.getRoot();
        }

        // Post view initialization logic
        @Override
        public void onViewCreated(@NonNull View view,
                @Nullable Bundle savedInstanceState) {

            // Connect adapters
            binding.productsList.setAdapter(productAdapter);

            // Initialize ViewModels and other dependencies
            ProductListViewModel viewModel = ViewModelProviders
                    .of(this)
                    .get(ProductListViewModel.class);

            // Initialize view properties, set click listeners, etc.
            binding.productsSearchBtn.setOnClickListener(...)

            // Subscribe to state
            viewModel.getProducts().observe(this, myProducts -> {
                ...
           });

           // ...and so on

        // Provided to ProductAdapter
        private ProductClickCallback productClickCallback = new ProductClickCallback() {
            @Override
            public void onClick(Product product) {
                if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
                    ((ProductListActivity) requireActivity()).show(product);
                }
            }
        };
        ...
    }
    

Em ProductListFragment, observe que não há chamada para setContentView() para aumentar e conectar o layout. Em um fragmento, onCreateView() inicializa a visualização raiz. onCreateView() usa uma instância de um LayoutInflater que pode ser usada para aumentar a visualização raiz com base em um arquivo de recurso de layout. Este exemplo reutiliza o layout product_list existente que foi usado pela atividade porque nada precisa mudar no layout propriamente dito.

Se você tiver alguma lógica de IU residente nas funções onStart(), onResume(), onPause() ou onStop() da atividade que não estejam relacionadas à navegação, mova-as para funções correspondentes de mesmo nome no fragmento.

Inicializar o fragmento na atividade do host

Depois de mover toda a lógica da IU para o fragmento, apenas a lógica de navegação permanecerá na atividade.

Kotlin

    class ProductListActivity : AppCompatActivity() {

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.product_list_host)
        }

        fun show(product: Product) {
            val intent = Intent(this, ProductActivity::class.java)
            intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.id)
            startActivity(intent)
        }
    }
    

Java

    public class ProductListActivity extends AppCompatActivity {

        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.product_list_host);
        }

        public void show(Product product) {
            Intent intent = new Intent(this, ProductActivity.class);
            intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.getId());
            startActivity(intent);
        }
    }
    

A última etapa é criar uma instância do fragmento em onCreate(), logo após configurar a visualização de conteúdo:

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.product_list_host)

        if (savedInstanceState == null) {
            val fragment = ProductListFragment()
            supportFragmentManager
                    .beginTransaction()
                    .add(R.id.main_content, fragment)
                    .commit()
        }
    }
    

Java

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.product_list_host);

        if (savedInstanceState == null) {
            ProductListFragment fragment = new ProductListFragment();
            getSupportFragmentManager()
                    .beginTransaction()
                    .add(R.id.main_content, fragment)
                    .commit();
        }
    }
    

Como mostrado neste exemplo, FragmentManager salva e restaura automaticamente fragmentos nas mudanças de configuração. Portanto, você só precisa adicionar o fragmento se savedInstanceState for nulo.

Passar extras de intent para o fragmento

Se a atividade receber Extras por meio de um intent, você poderá passá-la para o fragmento diretamente como argumento.

Neste exemplo, o ProductDetailsFragment recebe os argumentos diretamente dos extras de intent da atividade:

Kotlin

    ...

    if (savedInstanceState == null) {
        val fragment = ProductDetailsFragment()

        // Intent extras and Fragment Args are both of type android.os.Bundle.
        fragment.arguments = intent.extras

        supportFragmentManager
                .beginTransaction()
                .add(R.id.main_content, fragment)
                .commit()
    }

    ...
    

Java

    ...

    if (savedInstanceState == null) {
        ProductDetailsFragment fragment = new ProductDetailsFragment();

        // Intent extras and fragment Args are both of type android.os.Bundle.
        fragment.setArguments(getIntent().getExtras());

        getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.main_content, fragment)
                .commit();
    }

    ...
    

Neste ponto, você poderá testar a execução do app com a primeira tela atualizada para usar um fragmento. Continue a migração do restante das telas baseadas em atividades e reserve um tempo para testar após cada iteração.

Integrar a Navigation component

Depois de usar uma arquitetura baseada em fragmento, você estará pronto para começar a integrar a Navigation component.

Primeiro, adicione as dependências de navegação mais recentes ao projeto, seguindo as instruções nas notas da versão da biblioteca de navegação.

Criar um gráfico de navegação

A Navigation component representa a configuração de navegação do app em um arquivo de recursos como um gráfico, assim como as visualizações do app são representadas. Isso ajuda a manter a navegação do app organizada fora do codebase e fornece uma maneira de editar visualmente a navegação do app.

Para criar um gráfico de navegação, comece criando uma nova pasta de recursos chamada navigation. Para adicionar o gráfico, clique com o botão direito do mouse nesse diretório e escolha New > Navigation resource file.

A Navigation component usa uma atividade como um host para navegação e troca fragmentos individuais nesse host à medida que os usuários navegam pelo app. Antes de começar a organizar visualmente a navegação do app, você precisa configurar um NavHost dentro da atividade que hospedará esse gráfico. Como estamos usando fragmentos, podemos usar a implementação do NavHost padrão da Navigation component, NavHostFragment.

Um NavHostFragment é configurado por meio de um elemento <fragment> colocado dentro de uma atividade de host, como mostrado no exemplo a seguir:

<fragment
       android:name="androidx.navigation.fragment.NavHostFragment"
       app:navGraph="@navigation/product_list_graph"
       app:defaultNavHost="true"
       android:id="@+id/main_content"
       android:layout_width="match_parent"
       android:layout_height="match_parent" />
    

O atributo app:NavGraph aponta para o gráfico de navegação associado a este host de navegação. A definição dessa propriedade aumenta o gráfico de navegação e define a propriedade do gráfico em NavHostFragment. O atributo app:defaultNavHost garante que o NavHostFragment intercepte o botão "Voltar" do sistema.

Se você estiver usando a navegação de nível superior, como DrawerLayout ou BottomNavigationView, este <fragment> substitui o principal elemento de visualização de conteúdo. Consulte Atualizar componentes de IU com NavigationUI para ver exemplos.

Para um layout simples, você pode incluir este elemento <fragment> como derivado do ViewGroup raiz:

<FrameLayout
       xmlns:app="http://schemas.android.com/apk/res-auto"
       xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_height="match_parent"
       android:layout_width="match_parent">

    <fragment
       android:id="@+id/main_content"
       android:name="androidx.navigation.fragment.NavHostFragment"
       app:navGraph="@navigation/product_list_graph"
       app:defaultNavHost="true"
       android:layout_width="match_parent"
       android:layout_height="match_parent" />

    </FrameLayout>
    

Se você clicar na guia Design na parte inferior, verá um gráfico semelhante ao mostrado abaixo. No lado superior esquerdo do gráfico, em Destinations, você pode ver uma referência à atividade NavHost na forma de layout_name (resource_id).

Clique no botão de adição perto do topo para adicionar os fragmentos a este gráfico.

A Navigation component refere-se a telas individuais como destinos. Os destinos podem ser fragmentos, atividades ou destinos personalizados. Você pode adicionar qualquer tipo de destino ao gráfico, mas os destinos de atividades são considerados destinos terminais, porque quando você navega para um destino de atividade, opera em um host de navegação e um gráfico separados.

A Navigation component refere-se à maneira como os usuários vão de um destino para outro como ações. As ações também podem descrever animações de transição e comportamentos de transição de tela.

Remover transações de fragmentos

Agora que você está usando a Navigation component, se estiver navegando entre telas baseadas em fragmentos com a mesma atividade, poderá remover as interações de FragmentManager.

Se o app estiver usando vários fragmentos na mesma atividade ou navegação de nível superior, como layout de gaveta ou navegação inferior, você provavelmente está usando FragmentManager e FragmentTransactions para adicionar ou substituir fragmentos na seção de conteúdo principal da interface. Agora, isso pode ser substituído e simplificado usando a Navigation component, fornecendo ações para vincular destinos dentro do gráfico e navegando usando NavController.

Veja alguns cenários que você pode encontrar e como abordar a migração para cada um deles.

Atividade única para gerenciar vários fragmentos

Se você tiver uma única atividade que gerencia vários fragmentos, o código de atividade pode ter esta aparência:

Kotlin

    class MainActivity : AppCompatActivity() {

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)

            // Logic to load the starting destination
            //  when the Activity is first created
            if (savedInstanceState == null) {
                val fragment = ProductListFragment()
                supportFragmentManager.beginTransaction()
                        .add(R.id.fragment_container, fragment, ProductListFragment.TAG)
                        .commit()
            }
        }

        // Logic to navigate the user to another destination.
        // This may include logic to initialize and set arguments on the destination
        // fragment or even transition animations between the fragments (not shown here).
        fun navigateToProductDetail(productId: String) {
            val fragment = new ProductDetailsFragment()
            val args = Bundle().apply {
                putInt(KEY_PRODUCT_ID, productId)
            }
            fragment.arguments = args

            supportFragmentManager.beginTransaction()
                    .addToBackStack(ProductDetailsFragment.TAG)
                    .replace(R.id.fragment_container, fragment, ProductDetailsFragment.TAG)
                    .commit()
        }
    }
    

Java

    public class MainActivity extends AppCompatActivity {

        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            // Logic to load the starting destination when the activity is first created.
            if (savedInstanceState == null) {
                val fragment = ProductListFragment()
                supportFragmentManager.beginTransaction()
                        .add(R.id.fragment_container, fragment, ProductListFragment.TAG)
                        .commit();
            }
        }

        // Logic to navigate the user to another destination.
        // This may include logic to initialize and set arguments on the destination
        // fragment or even transition animations between the fragments (not shown here).
        public void navigateToProductDetail(String productId) {
            Fragment fragment = new ProductDetailsFragment();
            Bundle args = new Bundle();
            args.putInt(KEY_PRODUCT_ID, productId);
            fragment.setArguments(args);

            getSupportFragmentManager().beginTransaction()
                    .addToBackStack(ProductDetailsFragment.TAG)
                    .replace(R.id.fragment_container, fragment, ProductDetailsFragment.TAG)
                    .commit();
        }
    }
    

Dentro do destino de origem, você pode estar invocando uma função de navegação em resposta a algum evento, conforme mostrado abaixo:

Kotlin

    class ProductListFragment : Fragment() {
        ...
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            // In this example a callback is passed to respond to an item clicked
            //  in a RecyclerView
            productAdapter = ProductAdapter(productClickCallback)
            binding.productsList.setAdapter(productAdapter)
        }
        ...

        // The callback makes the call to the activity to make the transition.
        private val productClickCallback = ProductClickCallback { product ->
                (requireActivity() as MainActivity).navigateToProductDetail(product.id)
        }
    }
    

Java

    public class ProductListFragment extends Fragment  {
        ...
        @Override
        public void onViewCreated(@NonNull View view,
                @Nullable Bundle savedInstanceState) {
        // In this example a callback is passed to respond to an item clicked in a RecyclerView
            productAdapter = new ProductAdapter(productClickCallback);
            binding.productsList.setAdapter(productAdapter);
        }
        ...

        // The callback makes the call to the activity to make the transition.
        private ProductClickCallback productClickCallback = product -> (
            ((MainActivity) requireActivity()).navigateToProductDetail(product.getId())
        );
    }
    

Isso pode ser substituído por meio da atualização do gráfico de navegação para definir o destino inicial e as ações para vincular os destinos e definir argumentos onde necessário:

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/product_list_graph"
        app:startDestination="@id/product_list">

        <fragment
            android:id="@+id/product_list"
            android:name="com.example.android.persistence.ui.ProductListFragment"
            android:label="Product List"
            tools:layout="@layout/product_list">
            <action
                android:id="@+id/navigate_to_product_detail"
                app:destination="@id/productDetail" />
        </fragment>
        <fragment
            android:id="@+id/product_detail"
            android:name="com.example.android.persistence.ui.ProductDetailFragment"
            android:label="Product Detail"
            tools:layout="@layout/product_detail">
            <argument
                android:name="product_id"
                app:argType="integer" />
        </fragment>
    </navigation>
    

Em seguida, você pode atualizar a atividade:

Kotlin

    class MainActivity : AppCompatActivity() {

        // No need to load the start destination, handled automatically by the Navigation component
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
        }
    }
    

Java

    public class MainActivity extends AppCompatActivity {

        // No need to load the start destination, handled automatically by the Navigation component
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    }
    

A atividade não precisa mais de um método navigateToProductDetail(). Na próxima seção, atualizamos ProductListFragment para usar o NavController para navegar para a próxima tela de detalhes do produto.

Passar argumentos com segurança

A Navigation component tem um plug-in para Gradle chamado Safe Args que gera objetos simples e classes do builder para acesso seguro a argumentos especificados para destinos e ações.

Depois que o plug-in é aplicado, os argumentos definidos em um destino no gráfico de navegação fazem com que a estrutura da Navigation component gere uma classe Arguments que fornece argumentos de tipos seguros para o destino. Definir uma ação faz com que o plug-in gere uma classe de configuração Directions que pode ser usada para informar ao NavController como navegar o usuário até o destino. Quando uma ação aponta para um destino que requer argumentos, a classe Directions gerada inclui métodos de construtor que exigem esses parâmetros.

Dentro do fragmento, use NavController e a classe Directions gerada para fornecer argumentos de tipo seguro ao destino, como mostrado no exemplo a seguir:

Kotlin

    class ProductListFragment : Fragment() {

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            // In this example a callback is passed to respond to an item clicked in a RecyclerView
            productAdapter = ProductAdapter(productClickCallback)
            binding.productsList.setAdapter(productAdapter)
        }
        ...

        // The callback makes the call to the NavController to make the transition.
        private val productClickCallback = ProductClickCallback { product ->
            val directions = ProductListDirections.navigateToProductDetail(product.id)
            findNavController().navigate(directions)
        }
    }
    

Java

    public class ProductListFragment extends Fragment  {
        ...
        @Override
        public void onViewCreated(@NonNull View view,
                @Nullable Bundle savedInstanceState) {
            // In this example a callback is passed to respond to an item clicked in a RecyclerView
            productAdapter = new ProductAdapter(productClickCallback);
            binding.productsList.setAdapter(productAdapter);
        }
        ...

        // The callback makes the call to the activity to make the transition.
        private ProductClickCallback productClickCallback = product -> {
            ProductListDirections.ViewProductDetails directions =
                    ProductListDirections.navigateToProductDetail(product.getId());
            NavHostFragment.findNavController(this).navigate(directions);
        };
    }
    

Navegação de nível superior

Se o app usa um DrawerLayout, você pode ter muita lógica de configuração na atividade que gerencia abrir e fechar a gaveta e navegar para outros destinos.

A atividade resultante terá esta aparência:

Kotlin

    class MainActivity : AppCompatActivity(),
        NavigationView.OnNavigationItemSelectedListener {

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)

            val toolbar: Toolbar = findViewById(R.id.toolbar)
            setSupportActionBar(toolbar)

            val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
            val navView: NavigationView = findViewById(R.id.nav_view)
            val toggle = ActionBarDrawerToggle(
                    this,
                    drawerLayout,
                    toolbar,
                    R.string.navigation_drawer_open,
                    R.string.navigation_drawer_close
            )
            drawerLayout.addDrawerListener(toggle)
            toggle.syncState()

            navView.setNavigationItemSelectedListener(this)
        }

        override fun onBackPressed() {
            val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
            if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
                drawerLayout.closeDrawer(GravityCompat.START)
            } else {
                super.onBackPressed()
            }
        }

        override fun onNavigationItemSelected(item: MenuItem): Boolean {
            // Handle navigation view item clicks here.
            when (item.itemId) {
                R.id.home -> {
                    val homeFragment = HomeFragment()
                    show(homeFragment)
                }
                R.id.gallery -> {
                    val galleryFragment = GalleryFragment()
                    show(galleryFragment)
                }
                R.id.slide_show -> {
                    val slideShowFragment = SlideShowFragment()
                    show(slideShowFragment)
                }
                R.id.tools -> {
                    val toolsFragment = ToolsFragment()
                    show(toolsFragment)
                }
            }
            val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
            drawerLayout.closeDrawer(GravityCompat.START)
            return true
        }
    }

    private fun show(fragment: Fragment) {

        val drawerLayout = drawer_layout as DrawerLayout
        val fragmentManager = supportFragmentManager

        fragmentManager
                .beginTransaction()
                .replace(R.id.main_content, fragment)
                .commit()

        drawerLayout.closeDrawer(GravityCompat.START)
    }
    

Java

    public class MainActivity extends AppCompatActivity
            implements NavigationView.OnNavigationItemSelectedListener {

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            Toolbar toolbar = findViewById(R.id.toolbar);
            setSupportActionBar(toolbar);

            DrawerLayout drawer = findViewById(R.id.drawer_layout);
            NavigationView navigationView = findViewById(R.id.nav_view);
            ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                    this,
                    drawer,
                    toolbar,
                    R.string.navigation_drawer_open,
                    R.string.navigation_drawer_close);
            drawer.addDrawerListener(toggle);
            toggle.syncState();

            navigationView.setNavigationItemSelectedListener(this);
        }

        @Override
        public void onBackPressed() {
            DrawerLayout drawer = findViewById(R.id.drawer_layout);
            if (drawer.isDrawerOpen(GravityCompat.START)) {
                drawer.closeDrawer(GravityCompat.START);
            } else {
                super.onBackPressed();
            }
        }

        @Override
        public boolean onNavigationItemSelected(MenuItem item) {
            // Handle navigation view item clicks here.
            int id = item.getItemId();

            if (id == R.id.home) {
                Fragment homeFragment = new HomeFragment();
                show(homeFragment);
            } else if (id == R.id.gallery) {
                Fragment galleryFragment = new GalleryFragment();
                show(galleryFragment);
            } else if (id == R.id.slide_show) {
                Fragment slideShowFragment = new SlideShowFragment();
                show(slideShowFragment);
            } else if (id == R.id.tools) {
                Fragment toolsFragment = new ToolsFragment();
                show(toolsFragment);
            }

            DrawerLayout drawer = findViewById(R.id.drawer_layout);
            drawer.closeDrawer(GravityCompat.START);
            return true;
        }

        private void show(Fragment fragment) {

            DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
            FragmentManager fragmentManager = getSupportFragmentManager();

            fragmentManager
                    .beginTransaction()
                    .replace(R.id.main_content, fragment)
                    .commit();

            drawerLayout.closeDrawer(GravityCompat.START);
        }
    }
    

Depois de adicionar a Navigation component ao projeto e criar um gráfico de navegação, adicione cada um dos destinos de conteúdo do gráfico, como Home, Gallery, SlideShow e Tools do exemplo acima. Certifique-se de que os valores do item de menu id correspondem ao destino associado id, conforme mostrado abaixo:

<!-- activity_main_drawer.xml -->
    <menu xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        tools:showIn="navigation_view">

        <group android:checkableBehavior="single">
            <item
                android:id="@+id/home"
                android:icon="@drawable/ic_menu_camera"
                android:title="@string/menu_home" />
            <item
                android:id="@+id/gallery"
                android:icon="@drawable/ic_menu_gallery"
                android:title="@string/menu_gallery" />
            <item
                android:id="@+id/slide_show"
                android:icon="@drawable/ic_menu_slideshow"
                android:title="@string/menu_slideshow" />
            <item
                android:id="@+id/tools"
                android:icon="@drawable/ic_menu_manage"
                android:title="@string/menu_tools" />
        </group>
    </menu>
    
<!-- activity_main_graph.xml -->
    <navigation xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/main_graph"
        app:startDestination="@id/home">

        <fragment
            android:id="@+id/home"
            android:name="com.example.HomeFragment"
            android:label="Home"
            tools:layout="@layout/home" />

        <fragment
            android:id="@+id/gallery"
            android:name="com.example.GalleryFragment"
            android:label="Gallery"
            tools:layout="@layout/gallery" />

        <fragment
            android:id="@+id/slide_show"
            android:name="com.example.SlideShowFragment"
            android:label="Slide Show"
            tools:layout="@layout/slide_show" />

        <fragment
            android:id="@+id/tools"
            android:name="com.example.ToolsFragment"
            android:label="Tools"
            tools:layout="@layout/tools" />

    </navigation>
    

Se você corresponder aos valores id do menu e do gráfico, poderá conectar NavController para esta atividade para gerenciar a navegação automaticamente com base no item do menu. O NavController também gerencia adequadamente como abrir e fechar o DrawerLayout, além do comportamento do botão "Para cima" e "Voltar".

O MainActivity pode ser atualizado para conectar NavController a Toolbar e NavigationView.

Veja o seguinte snippet para um exemplo:

Kotlin

    class MainActivity : AppCompatActivity()  {

        val drawerLayout by lazy { findViewById<DrawerLayout>(R.id.drawer_layout) }
        val navController by lazy { findNavController(R.id.main_content) }
        val navigationView by lazy { findViewById<NavigationView>(R.id.nav_view) }

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)

            val toolbar = findViewById<Toolbar>(R.id.toolbar)
            setSupportActionBar(toolbar)

            // Show and Manage the Drawer and Back Icon
            setupActionBarWithNavController(navController, drawerLayout)

            // Handle Navigation item clicks
            // This works with no further action on your part if the menu and destination id’s match.
            navigationView.setupWithNavController(navController)

        }

        override fun onSupportNavigateUp(): Boolean {
            // Allows NavigationUI to support proper up navigation or the drawer layout
            // drawer menu, depending on the situation
            return navController.navigateUp(drawerLayout)
        }
    }
    

Java

    public class MainActivity extends AppCompatActivity {

        private DrawerLayout drawerLayout;
        private NavController navController;
        private NavigationView navigationView;

        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {

            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            drawerLayout = findViewById(R.id.drawer_layout);
            navController = Navigation.findNavController(this, R.id.content_main);
            navigationView = findViewById(R.id.nav_view);

            Toolbar toolbar = findViewById(R.id.toolbar);
            setSupportActionBar(toolbar);

            // Show and Manage the Drawer and Back Icon
            NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout);

            // Handle Navigation item clicks
            // This works with no further action on your part if the menu and destination id’s match.
            NavigationUI.setupWithNavController(navigationView, navController);

        }

        @Override
        public boolean onSupportNavigateUp() {
            // Allows NavigationUI to support proper up navigation or the drawer layout
            // drawer menu, depending on the situation.
            return NavigationUI.navigateUp(navController, drawerLayout);

        }
    }
    

Você pode usar essa mesma técnica com a navegação baseada em BottomNavigationView e em menus. Consulte Atualizar componentes de IU com NavigationUI para ver mais exemplos.

Adicionar destinos de atividades

Quando cada tela do app estiver programada para usar a Navigation component e você não estiver mais usando FragmentTransactions para fazer a transição entre destinos baseados em fragmento, a próxima etapa será eliminar chamadas startActivity.

Primeiro, identifique locais no app em que você tem dois gráficos de navegação separados e está usando startActivity para fazer a transição entre eles.

Este exemplo contém dois gráficos (A e B) e uma chamada startActivity() para fazer a transição de A para B.

Kotlin

    fun navigateToProductDetails(productId: String) {
        val intent = Intent(this, ProductDetailsActivity::class.java)
        intent.putExtra(KEY_PRODUCT_ID, productId)
        startActivity(intent)
    }
    

Java

    private void navigateToProductDetails(String productId) {
        Intent intent = new Intent(this, ProductDetailsActivity.class);
        intent.putExtra(KEY_PRODUCT_ID, productId);
        startActivity(intent);
    

A seguir, substitua-os por um destino da atividade no gráfico A que representa a navegação para a atividade do host do gráfico B. Se você tiver argumentos para passar para o destino inicial do gráfico B, poderá designá-los na definição de destino da atividade.

No exemplo a seguir, o gráfico A define um destino de atividade que leva o argumento product_id com uma ação. O gráfico B não contém mudanças.

A representação XML dos gráficos A e B pode ter esta aparência:

<!-- Graph A -->
    <navigation xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/product_list_graph"
        app:startDestination="@id/product_list">

        <fragment
            android:id="@+id/product_list"
            android:name="com.example.android.persistence.ui.ProductListFragment"
            android:label="Product List"
            tools:layout="@layout/product_list_fragment">
            <action
                android:id="@+id/navigate_to_product_detail"
                app:destination="@id/product_details_activity" />
        </fragment>

        <activity
            android:id="@+id/product_details_activity"
            android:name="com.example.android.persistence.ui.ProductDetailsActivity"
            android:label="Product Details"
            tools:layout="@layout/product_details_host">

            <argument
                android:name="product_id"
                app:argType="integer" />

        </activity>

    </navigation>
    
<!-- Graph B -->
    <navigation xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        app:startDestination="@id/product_details">

        <fragment
            android:id="@+id/product_details"
            android:name="com.example.android.persistence.ui.ProductDetailsFragment"
            android:label="Product Details"
            tools:layout="@layout/product_details_fragment">
            <argument
                android:name="product_id"
                app:argType="integer" />
        </fragment>

    </navigation>
    

Você pode navegar para a atividade de host do gráfico B usando os mesmos mecanismos usados para navegar para destinos de fragmento:

Kotlin

    fun navigateToProductDetails(productId: String) {
        val directions = ProductListDirections.navigateToProductDetail(productId)
        findNavController().navigate(directions)
    }
    

Java

    private void navigateToProductDetails(String productId) {
        ProductListDirections.NavigateToProductDetail directions =
                ProductListDirections.navigateToProductDetail(productId);
        Navigation.findNavController(getView()).navigate(directions);
    

Passar argumentos de destino da atividade para um fragmento de destino inicial

Se a atividade de destino receber extras, como no exemplo anterior, você poderá passá-los diretamente ao destino como argumentos, mas precisará definir manualmente o gráfico de navegação do host dentro do método onCreate() da atividade do host para transmitir os extras de intent como argumentos para o fragmento, como mostrado abaixo:

Kotlin

    class ProductDetailsActivity : AppCompatActivity() {

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.product_details_host)
            findNavController(R.id.main_content)
                    .setGraph(R.navigation.product_detail_graph, intent.extras)
        }

    }
    

Java

    public class ProductDetailsActivity extends AppCompatActivity {

        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            setContentView(R.layout.product_details_host);
            findNavController(R.id.main_content)
                    .setGraph(R.navigation.product_detail_graph, getIntent().getExtras());
        }

    }
    

Os dados podem ser extraídos dos argumentos de fragmento Bundle usando a classe de argumentos gerada, como mostrado no exemplo a seguir:

Kotlin

    class ProductDetailsFragment : Fragment() {

        val args by navArgs<ProductDetailsArgs>()

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            val productId = args.productId
            ...
        }
        ...
    

Java

    public class ProductDetailsFragment extends Fragment {

        ProductDetailsArgs args;

        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            args = ProductDetailsArgs.fromBundle(requireArguments());
        }

        @Override
        public void onViewCreated(@NonNull View view,
                @Nullable Bundle savedInstanceState) {
           int productId = args.getProductId();
           ...
        }
        ...
    

Combinar atividades

Você pode combinar gráficos de navegação nos casos em que várias atividades compartilham o mesmo layout, como um FrameLayout simples contendo um único fragmento. Na maioria dos casos, basta combinar todos os elementos de cada gráfico de navegação e atualizar quaisquer elementos de destino da atividade para destinos de fragmento.

O exemplo a seguir combina os gráficos A e B da seção anterior:

Antes da combinação:

<!-- Graph A -->
    <navigation xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/product_list_graph"
        app:startDestination="@id/product_list">

        <fragment
            android:id="@+id/product_list"
            android:name="com.example.android.persistence.ui.ProductListFragment"
            android:label="Product List Fragment"
            tools:layout="@layout/product_list">
            <action
                android:id="@+id/navigate_to_product_detail"
                app:destination="@id/product_details_activity" />
        </fragment>
        <activity
            android:id="@+id/product_details_activity"
            android:name="com.example.android.persistence.ui.ProductDetailsActivity"
            android:label="Product Details Host"
            tools:layout="@layout/product_details_host">
            <argument android:name="product_id"
                app:argType="integer" />
        </activity>

    </navigation>
    
<!-- Graph B -->
    <navigation xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/product_detail_graph"
        app:startDestination="@id/product_details">

        <fragment
            android:id="@+id/product_details"
            android:name="com.example.android.persistence.ui.ProductDetailsFragment"
            android:label="Product Details"
            tools:layout="@layout/product_details">
            <argument
                android:name="product_id"
                app:argType="integer" />
        </fragment>
    </navigation>
    

Após a combinação:

<!-- Combined Graph A and B -->
    <navigation xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/product_list_graph"
        app:startDestination="@id/product_list">

        <fragment
            android:id="@+id/product_list"
            android:name="com.example.android.persistence.ui.ProductListFragment"
            android:label="Product List Fragment"
            tools:layout="@layout/product_list">
            <action
                android:id="@+id/navigate_to_product_detail"
                app:destination="@id/product_details" />
        </fragment>

        <fragment
            android:id="@+id/product_details"
            android:name="com.example.android.persistence.ui.ProductDetailsFragment"
            android:label="Product Details"
            tools:layout="@layout/product_details">
            <argument
                android:name="product_id"
                app:argType="integer" />
        </fragment>

    </navigation>

    

Manter os nomes das ações iguais ao mesclar pode tornar esse processo contínuo, não exigindo alterações na base do código existente. Por exemplo, navigateToProductDetail permanece igual aqui. A única diferença é que essa ação agora representa a navegação para um fragmento de destino dentro do mesmo NavHost em vez de um destino de atividade:

Kotlin

    fun navigateToProductDetails(productId: String) {
        val directions = ProductListDirections.navigateToProductDetail(productId)
        findNavController().navigate(directions)
    }
    

Java

    private void navigateToProductDetails(String productId) {
        ProductListDirections.NavigateToProductDetail directions =
                ProductListDirections.navigateToProductDetail(productId);
        Navigation.findNavController(getView()).navigate(directions);
    

Outros recursos

Para mais informações relacionadas à navegação, consulte os seguintes tópicos: