Navegación con Android [NavHostFragment]

17 de ago. de 2021

En cuanto a la navegación de fragmentos en android existen diferentes maneras de hacerlo, sin embargo una reciente y aconsejada es el uso de NavHostFragment, en este post veremos algunos conceptos de navegación y como implementarlos con el NavHostFragment en un pequeño proyecto.

Referente a la navegación en android, existen 3 principios que podemos resumir:

1er Principio: Siempre hay un punto de inicio!

Cuando sea que iniciamos la app se mostrara una primera pantalla que será la que nosotros deseemos.
En nuestro caso nuestra aplicación contara con varios fragmentos siento el FragmentA nuestro punto inicio

Nav1

Empezamos creando un proyecto en Android Studio con un blank activity, luego creamos nuestro primer fragmento
FragmentA

FragmentA.kt

package com.example.navhostfragmentdemo

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

class FragmentA : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_a, container, false)
    }
}

Con su respectivo layout

fragment_a.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".FragmentA">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:text="Fragment A"
        android:textColor="@color/black"
        android:textSize="48dp" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Go to fragment B" />
</LinearLayout>

De esta manera ya se tiene el FragmentA listo, faltando colocarlo en el layout del mainActivity de la siguiente forma

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <fragment
        android:id="@+id/fragmentA"
  android:name="com.example.navhostfragmentdemo.FragmentA"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

Y el main activity lo dejamos tal y como fue creado

MainActivity.kt

package com.example.navhostfragmentdemo

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

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

Pueden hacer correr el proyecto y el resultado será el siguiente

Nav1

Hasta ahora lo único que hicimos fue

  • Crear proyecto
  • Crear FragmentA
  • Insertar el FragmentA en el layout del Main activity

2do Principio:Siempre puedes volver atrás (Backstack)

El backstack es una estructura de almacenamiento de tipo FILO(First In Last Out), que almacena nuestras vistas que tengamos en la app.
Sea nuestro punto de inicio el FragmentA, al ir hacia otra vista(FragmentB), esta nueva vista será puesta encima de nuestro punto inicial, y lo mismo cuando se vaya del FragmentB al FragmentC

Nav1

Para la utilizar el navigation component debemos crear un resource de tipo Navigation que será el grafo contenedor de nuestros fragmentos

navigationComponent

Nota: En caso de que al crear el resource no les salga el mensaje de error de las bibliotecas lo pueden agregar manualmente en el build.gradle de su proyecto

build.gradle

dependencies {
    ...
    implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'
    implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'
}

En el principio #1 insertamos el FragmentA en el activitymain.xml, si usamos el navigation architectural component, el NavHostFragment debe ser el contenedor de todos los fragmentos, por tanto debemos reemplazarlo

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

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

</LinearLayout>

Asignamos un nuevo nombre al fragmento "myNavHostFragment" y cambiamos el tipo a androidx.navigation.fragment.NavHostFragment
También aumentamos la línea

app:defaultNavHost="true"

que interceptara cuando sea que apretemos el back button de android.
La línea

app:navGraph="@navigation/navigation"

nos sirve para especificar que resource usara el NavHostFragment

Con los pasos anteriores realizados nos encontramos listos para manipular nuestro grafo con los fragments que deseemos
Haciendo click en el navigation.xml que creamos, nos aparecerá una pantalla vacío, y agregamos el punto inicial que sería nuestro FragmentA haciendo click en el botón de New destination

De igual manera que se creó que el "FragmentA" podemos crear un "FragmentB" de similar estructura y los vinculamos como se observa a continuación

navigationComponentAddFragments

Y al momento de crear sus vínculos, se crean acciones, las cuales las usaremos posteriormente

navigationComponentAddLinks

Están vinculados, pero no hay una acción que haga que de un fragment vaya a otro, por tanto , debemos implementar un clicklistener al botón del FragmentA

FragmentA

 override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view  = inflater.inflate(R.layout.fragment_a, container, false)
        val buttonA = view.findViewById<Button>(R.id.actionButton)
        buttonA?.setOnClickListener{ view : View ->
            Navigation.findNavController(view).navigate(R.id.action_fragmentA_to_fragmentB)
        }
        return view.rootView
    }

Lo que hace la línea


Navigation.findNavController(view).navigate(R.id.action_fragmentA_to_fragmentB)

es encontrar el controlador de navegacion y navegar según la acción que se creó al momento de vincular el FragmentA con el FragmentB
Como resultado tenemos lo siguiente:
navigationComponentNavFail
Podemos notar que no tenemos el botón para volver atrás dentro la app, pero si responde al back button del dispositivo.
Para arreglar este problema, cuando usemos NavHostFragment debemos hacer unos cambios en el activity contenedor de la navegación que en este caso es el MainActivity, los métodos onCreate y sobrescribir el método onSupportNavigateUp

MainActivity

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val navController = Navigation.findNavController(this, R.id.myNavHostFragment)
        NavigationUI.setupActionBarWithNavController(this, navController)
    }


    override fun onSupportNavigateUp(): Boolean {
        return findNavController(R.id.myNavHostFragment).navigateUp()
                || super.onSupportNavigateUp()
    }

En el onCreate se encuentra el controlador de navegación y lo incrustamos justo en el actionBar.
Luego el override al método onSupportNavigateUp devolverá la respuesta que nuestro componente de navegación retorne o el default en caso de no estar configurado
navigationComponentNavSuccess
De tal manera ya estamos respetando el 2do principio de siempre poder volver atrás

3er principio: Up goes back (Mostly)

Este principio se basa en que existen dos maneras de volver atrás, una por el up button (flecha de la app que va atrás) y el back button(botón para volver del dispositivo).
El up button y el back button operan de la misma manera dentro la app, con la única excepción que cuando estemos en el punto de inicio, el FragmentA, el up button no tiene a donde mas volver atrás, así que simplemente desaparece o debería, pero el back button si funciona, saliendo de la aplicación.
Se agrego un FragmentC para terminar el experimento, funciona igual que el FragmentB, se muestra como funciona el up y el back button
navigationComponentFinal

Si desean mas información sobre esto pueden dirigirse a la documentacion:

El repositorio con todo lo avanzado
https://github.com/beldier/navHostFragmentIntro

Gracias por su atención, en otro post estaré explicando los safeargs, navegación condicional y otros truquitos.

Nos pasamos años sin vivir en absoluto, y de pronto toda nuestra vida se concentra en un solo instante (Wilde)

  • Cristal

¿Te gustó el contenido o lo que hacemos? ¡Cualquier colaboración es agradecida para mantener los servidores o crear proyectos!

Comentarios:

¡Genial! Te has suscrito con éxito.
¡Genial! Ahora, completa el checkout para tener acceso completo.
¡Bienvenido de nuevo! Has iniciado sesión con éxito.
Éxito! Su cuenta está totalmente activada, ahora tienes acceso a todo el contenido.