Fazer a migração para o componente de navegação

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.

Em geral, a migração envolve as seguintes etapas:

  1. Remover a lógica da IU específica da tela 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 uma 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 = new ViewModelProvider(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
        )
        return 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 = new ViewModelProvider(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 um componente de navegação

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 Navigation mais recentes ao projeto, seguindo as instruções nas notas da versão da biblioteca Navigation.

Criar um gráfico de navegação

O componente de navegação 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 do componente de navegação, NavHostFragment.

Um NavHostFragment é configurado por meio de uma FragmentContainerView colocada dentro de uma atividade de host, como mostrado no exemplo a seguir:

<androidx.fragment.app.FragmentContainerView
   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, esssa FragmentContainerView 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 esse elemento FragmentContainerView como filho 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">

<androidx.fragment.app.FragmentContainerView
   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.

O componente de navegação 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.

O componente de navegação 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.

Caso o app esteja 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 IU. Agora, isso pode ser substituído e simplificado usando o componente de navegação, 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/product_detail" />
    </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 do componente de navegação 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 um componente de navegação 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. Confirme se os valores do item de menu id são os mesmos no 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 os valores id do menu e do gráfico forem iguais, você poderá conectar NavController para esta atividade e 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 dos botões "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 {
      (supportFragmentManager.findFragmentById(R.id.main_content) as NavHostFragment).navController
    }
    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);
        NavHostFragment navHostFragment = (NavHostFragment)
            getSupportFragmentManager().findFragmentById(R.id.main_content);
        navController = navHostFragment.getNavController();
        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 o componente de navegação 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)
        val navHostFragment = supportFragmentManager.findFragmentById(R.id.main_content) as NavHostFragment
        val navController = navHostFramgent.navController
        navController
                .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);
        NavHostFragment navHostFragment = (NavHostFragment)
            getSupportFragmentManager().findFragmentById(R.id.main_content);
        NavController navController = navHostFragment.getNavController();
        navController
                .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);

Recursos adicionais

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