Cómo migrar al componente de Navigation

El componente de Navigation es una biblioteca que puede administrar la navegación compleja, la animación de transición, los vínculos directos y el paso de argumentos de comprobación de tiempo de compilación entre las pantallas de tu app.

Este documento sirve como guía general para migrar una aplicación existente con el fin de usar el componente de Navigation.

En un nivel alto, la migración implica cuatro pasos:

  1. Mover la lógica de la IU específica de la pantalla fuera de las actividades: Mueve la lógica de la IU de tu app fuera de las actividades y asegúrate de que cada actividad posea solo la lógica de los componentes de la IU de navegación global, como una Toolbar, mientras delegas la implementación de cada pantalla a un fragmento o destino personalizado.

  2. Integrar el componente de Navigation: Para cada actividad, compila un gráfico de navegación que contenga uno o más fragmentos administrados por esa actividad. Reemplaza las transacciones de fragmentos con operaciones de componentes de Navigation.

  3. Agregar destinos de actividad: Reemplaza llamadas de startActivity() con acciones que usen destinos de actividad.

  4. Combinar actividades: Combina gráficos de navegación en casos donde varias actividades comparten un diseño común.

Requisitos previos

En esta guía, se asume que ya migraste tu app para usar las bibliotecas de AndroidX. Si todavía no lo hiciste, antes de continuar, migra tu proyecto para usar AndroidX.

Mueve la lógica de la IU específica de la pantalla fuera de las actividades

Las actividades son componentes a nivel del sistema que facilitan una interacción gráfica entre tu app y Android. Las actividades se registran en el manifiesto de tu app a fin de que Android sepa qué actividades están disponibles para iniciarse. La clase de actividad también permite que tu app reaccione a los cambios de Android, como cuando la IU de tu app ingresa al primer plano o sale de él, gira, etc. La actividad también puede servir como un lugar para compartir el estado entre pantallas.

Dentro del contexto de tu app, las actividades deben servir como un host para la navegación y deben contener la lógica y el conocimiento de cómo hacer la transición entre pantallas, pasar datos, etc. Sin embargo, es mejor que administres los detalles de tu IU en una parte más pequeña y reutilizable de la IU. La implementación recomendada para este patrón son los fragmentos. Consulta Actividad única: Por qué, cuándo y cómo, para obtener más información sobre las ventajas de usar fragmentos. Navigation admite fragmentos mediante la dependencia navegación-fragmento. Navigation también admite tipos de destino personalizados.

Si tu app no está usando fragmentos, lo primero que debes hacer es migrar cada pantalla de tu app para usar un fragmento. No quitarás la actividad en este momento. En cambio, crearás un fragmento para representar la pantalla y separar la lógica de la IU por responsabilidad.

Presentación de fragmentos

Para ilustrar el proceso de presentación de fragmentos, comencemos con un ejemplo de una aplicación que consta de dos pantallas: una pantalla de lista de productos y una pantalla de detalles del producto. Al hacer clic en un producto en la pantalla de la lista, el usuario pasa a una pantalla de detalles para obtener más información sobre el producto.

En este ejemplo, las pantallas de lista y detalles son actividades separadas actualmente.

Crea un nuevo diseño para alojar la IU

Para introducir un fragmento, comienza creando un nuevo archivo de diseño para que la actividad aloje el fragmento. Esto reemplaza el diseño de vista de contenido actual de la actividad.

Para una vista simple, puedes usar un FrameLayout, como se muestra en el siguiente ejemplo 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" />
    

El atributo id hace referencia a la sección de contenido donde más tarde agregaremos el fragmento.

Luego, en la función onCreate() de tu actividad, modifica la referencia del archivo de diseño en la función onCreate de tu actividad para señalar este nuevo archivo de diseño:

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);
            ...
        }
    }
    

El diseño existente (product_list, en este ejemplo) se usa como vista de raíz para el fragmento que estás a punto de crear.

Cómo crear un fragmento

Crea un nuevo fragmento para administrar la IU de tu pantalla. Es una buena práctica ser coherente con el nombre de host de tu actividad. El fragmento a continuación usa ProductListFragment, por ejemplo:

Kotlin

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

Java

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

Cómo mover la lógica de actividad a un fragmento

Una vez creada la definición del fragmento, el próximo paso es mover la lógica de la IU para esta pantalla de la actividad al nuevo fragmento. Si provienen de una arquitectura basada en actividades, es probable que tengas bastante lógica de creación de vistas en la función onCreate() de tu actividad.

A continuación, verás un ejemplo de pantalla basada en actividades con lógica de IU que debemos 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
       }

    

Tu actividad también puede controlar cuándo y cómo el usuario navega a la siguiente pantalla, como se muestra en el siguiente ejemplo:

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 de tu fragmento, puedes distribuir este trabajo entre onCreateView() y onViewCreated(), con solo la lógica de navegación restante en la actividad:

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);
                }
            }
        };
        ...
    }
    

En ProductListFragment, ten en cuenta que no hay llamadas a setContentView() para inflar y conectar el diseño. En un fragmento, onCreateView() inicializa la vista raíz. onCreateView() toma una instancia de LayoutInflater que se puede usar para inflar la vista raíz en función de un archivo de recursos de diseño. En este ejemplo, se reutiliza el diseño product_list existente que usó la actividad porque nada necesita cambiar al diseño en sí.

Si tienes lógicas de IU almacenadas en las funciones onStart(), onResume(), onPause() o onStop() de tu actividad que no están relacionadas con la navegación, puedes moverlas a las funciones correspondientes del mismo nombre en el fragmento.

Cómo inicializar el fragmento en la actividad del host

Una vez que hayas movido toda la lógica de la IU al fragmento, solo la lógica de navegación debe permanecer en la actividad.

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);
        }
    }
    

El último paso es crear una instancia del fragmento en onCreate(), justo después de configurar la vista de contenido, de la siguiente manera:

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 se muestra en este ejemplo, FragmentManager guarda y restaura automáticamente fragmentos sobre los cambios de configuración, por lo que solo debes agregar el fragmento si savedInstanceState es nulo.

Cómo pasar extras del intent adicionales al fragmento

Si tu actividad recibe Extras a través de un intent, se pueden pasar al fragmento directamente como argumentos.

En este ejemplo, ProductDetailsFragment recibe sus argumentos directamente de los extras del intent de la actividad:

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();
    }

    ...
    

En este punto, deberías poder probar ejecutar tu app con la primera pantalla actualizada para usar un fragmento. Continúa migrando el resto de tus pantallas basadas en actividades y tómate tiempo para hacer pruebas después de cada iteración.

Integración del componente de Navigation

Una vez que estés utilizando una arquitectura basada en fragmentos, estarás listo para comenzar a integrar el componente de Navigation.

Primero, agrega las dependencias de Navigation más recientes a tu proyecto. Para ello, sigue las instrucciones en las Notas de la versión de la biblioteca de Navigation.

Cómo crear un gráfico de navegación

El componente de Navigation representa la configuración de navegación de tu app en un archivo de recursos como un gráfico, al igual que las vistas de tu app. De esta manera, la navegación de tu app se mantiene organizada fuera de tu base de código y te brinda una manera de editar visualmente la navegación de tu app.

Para crear un gráfico de navegación, comienza por la creación de una nueva carpeta de recursos llamada navigation. Para agregar el gráfico, haz clic con el botón derecho en este directorio y selecciona New > Navigation resource file.

El componente de Navigation utiliza una actividad como host para la navegación e intercambia fragmentos individuales en ese host a medida que los usuarios navegan por tu app. Antes de comenzar a diseñar visualmente la navegación de tu app, debes configurar un NavHost dentro de la actividad que alojará este gráfico. Como estamos usando fragmentos, podemos usar la implementación predeterminada NavHost del componente de Navigation, NavHostFragment.

Un NavHostFragment se configura mediante un elemento <fragment> colocado dentro de una actividad de host, como se muestra en el siguiente ejemplo:

<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" />
    

El atributo app:NavGraph apunta al gráfico de navegación asociado con este host de navegación. Cuando se configura esta propiedad, se infla el gráfico de navegación y se establece la propiedad del gráfico en NavHostFragment. El atributo app:defaultNavHost garantiza que tu NavHostFragment intercepte el botón Atrás del sistema.

Si utilizas la navegación de nivel superior, como DrawerLayout o BottomNavigationView, este <fragment> reemplazará tu elemento de vista de contenido principal. Para ver ejemplos, consulta Cómo actualizar los componentes de IU con NavigationUI.

Para un diseño simple, puedes incluir este elemento <fragment> como elemento secundario de la raíz ViewGroup:

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

Si haces clic en la pestaña Design en la parte inferior, deberías ver un gráfico similar al que se muestra a continuación. En la parte superior izquierda del gráfico, en Destinations, puedes ver una referencia a la actividad NavHost en la forma de layout_name (resource_id).

Haz clic en el botón de signo más cerca de la parte superior para agregar los fragmentos a este gráfico.

El componente de Navigation hace referencia a pantallas individuales como destinos. Los destinos pueden ser fragmentos, actividades o destinos personalizados. Puedes agregar cualquier tipo de destino a tu gráfico, pero ten en cuenta que los destinos de actividad se consideran destinos terminales, porque una vez que navegas a un destino de actividad, estás operando dentro de un host de navegación y un gráfico separados.

El componente de Navigation se refiere a la forma en que los usuarios pasan de un destino a otro como acciones. Las acciones también pueden describir animaciones de transición y comportamiento emergente.

Cómo quitar fragmentos de transacciones

Ahora que utilizas el componente de Navigation, si navegas entre pantallas basadas en fragmentos en la misma actividad, puedes quitar las interacciones FragmentManager.

Si tu app usa varios fragmentos en la misma actividad o navegación de nivel superior, como un diseño de panel o la navegación inferior, probablemente uses un FragmentManager y FragmentTransactions para agregar o reemplazar fragmentos en la sección de contenido principal de la IU. Ahora, esto se puede reemplazar y simplificar mediante el componente navegación proporcionando acciones para vincular destinos en tu gráfico y, luego, navegar con NavController.

A continuación, presentamos algunas situaciones que podrían surgir junto con la forma en que podrías abordar la migración para cada situación.

Actividad única que administra varios fragmentos

Si tienes una sola actividad que administra varios fragmentos, tu código de actividad puede verse de la siguiente manera:

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 del destino de origen, podrías estar invocando una función de navegación en respuesta a algún evento, como se muestra a continuación:

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())
        );
    }
    

Esto se puede reemplazar mediante la actualización de tu gráfico de navegación para establecer el destino de inicio y las acciones para vincular tus destinos y definir argumentos cuando sea necesario:

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

Luego, puedes actualizar tu actividad de la siguiente manera:

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);
        }
    }
    

La actividad ya no necesita un método navigateToProductDetail(). En la siguiente sección, actualizamos ProductListFragment a fin de usar NavController para navegar a la siguiente pantalla de detalles del producto.

Pasa argumentos con seguridad

El componente de Navigation tiene un complemento de Gradle llamado Safe Args que genera clases simples de objeto y compilador para el acceso seguro a tipos de argumentos especificados en destinos y acciones.

Una vez que se aplica el complemento, cualquier argumento definido en un destino de tu gráfico de navegación hace que el marco del componente de Navigation genere una clase Arguments que proporciona argumentos de tipo seguro para el destino objetivo. La definición de una acción hace que el complemento genere una clase de configuración Directions que se puede usar para indicarle a NavController cómo desplazar al usuario al destino objetivo. Cuando una acción apunta a un destino que requiere argumentos, la clase Directions generada incluye métodos de constructor que requieren esos parámetros.

Dentro del fragmento, usa NavController y la clase Directions generada para proporcionar argumentos de tipo seguro al destino objetivo, como se muestra en el siguiente ejemplo:

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);
        };
    }
    

Navegación de nivel superior

Si tu app utiliza un DrawerLayout, es posible que tengas mucha lógica de configuración en tu actividad que gestiona la apertura y el cierre del panel lateral y la navegación a otros destinos.

Tu actividad resultante podría verse de la siguiente manera:

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);
        }
    }
    

Después de agregar el componente de Navigation a tu proyecto y crear un gráfico de navegación, agrega cada uno de los destinos de contenido desde tu gráfico (como Inicio, Galería, Presentación de diapositivas y Herramientas del ejemplo anterior). Asegúrate de que los valores de tu id de elemento de menú coincidan con los valores de tu id de destino asociados, como se muestra a continuación:

<!-- 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>
    

Si haces coincidir los valores de id de tu menú y tu gráfico, puedes vincular NavController para esta actividad a fin de administrar la navegación automáticamente según el elemento del menú. NavController también controla la apertura y el cierre de DrawerLayout y la administración adecuada del comportamiento del botón Arriba y el botón Atrás.

MainActivity se puede actualizar para conectar NavController a Toolbar y NavigationView.

Consulta el siguiente fragmento para ver un ejemplo:

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);

        }
    }
    

Puedes usar esta misma técnica con la navegación basada en BottomNavigationView y la navegación basada en menús. Consulta Cómo actualizar componentes de IU con NavigationUI para obtener más ejemplos.

Cómo agregar destinos de actividad

Una vez que cada pantalla de tu aplicación está conectada para usar el componente de Navigation, y ya no estás utilizando FragmentTransactions para hacer la transición entre destinos basados en fragmentos, el siguiente paso es eliminar las llamadas de startActivity.

Primero, identifica los lugares de tu app donde tienes dos gráficos de navegación separados y estás utilizando startActivity para hacer la transición entre ellos.

En este ejemplo, hay dos gráficos (A y B) y una llamada de startActivity() para hacer la transición de A a 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);
    

Luego, reemplázalos con un destino de actividad en el gráfico A que represente la navegación a la actividad host del gráfico B. Si tienes argumentos para pasar al destino de inicio del gráfico B, puedes designarlos en la definición de la actividad de destino.

En el siguiente ejemplo, el gráfico A define un destino de actividad que toma un argumento product_id junto con una acción. El gráfico B no contiene cambios.

La representación XML de los gráficos A y B podría verse de la siguiente manera:

<!-- 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>
    

Puedes navegar a la actividad del host del gráfico B con los mismos mecanismos que usas para navegar a destinos de fragmentos:

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);
    

Cómo pasar los argumentos de destino de actividad a un fragmento de destino de inicio

Si la actividad de destino recibe extras, como en el ejemplo anterior, puedes pasarlos al destino de inicio directamente como argumentos, pero debes establecer manualmente el gráfico de navegación del host dentro del método onCreate() de la actividad del host para poder pasar los extras del intent como argumentos para el fragmento, como se muestra a continuación:

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());
        }

    }
    

Los datos se pueden extraer de los argumentos del fragmento Bundle mediante la clase args generada, como se muestra en el siguiente ejemplo:

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();
           ...
        }
        ...
    

Cómo combinar actividades

Puedes combinar gráficos de navegación en los casos en que varias actividades compartan el mismo diseño, como un FrameLayout simple que contenga un solo fragmento. En la mayoría de estos casos, puedes combinar todos los elementos de cada gráfico de navegación y actualizar los elementos de destino de la actividad para fragmentar los destinos.

En el siguiente ejemplo, se combinan los gráficos A y B de la sección anterior:

Antes de combinar:

<!-- 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>
    

Después de combinar:

<!-- 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>

    

Mantener los nombres de tus acciones sin modificar durante la fusión puede hacer que este sea un proceso continuo, que no requiera cambios en tu base de código existente. Por ejemplo, navigateToProductDetail permanece igual aquí. La única diferencia es que esta acción ahora representa la navegación a un destino de fragmento dentro del mismo NavHost en lugar de un destino de actividad:

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 adicionales

Para obtener más información relacionada con la navegación, consulta los siguientes temas: