Como usar el Android Navigation Component

gvetri

Giuseppe Vetri

Posted on May 1, 2019

Como usar el Android Navigation Component

Android Architecture components - Navigation Component

El problema de la navegación

A lo largo de los años los desarrolladores Android han buscado una forma de hacer más fácil la navegación entre los fragments y las activities dentro de las aplicaciones que creaban. Muchos inventaron una cantidad de clases y variantes llamadas Navigation, Base Navigation, Navigation Manager.

Entre cualquier cantidad de ingeniosos nombres que el ser humano se puede inventar, cada uno tenía sus peculiaridades con sus ventajas y sus desventajas, muchas de estas necesitaban que el desarrollador heredara de una clase base o agregara manualmente constantes a una clase para así identificar a donde iría y de a donde vendría, esto solía generar muchos bugs respecto a la navegación de la app.

Por ejemplo, si una App que mostraba el detalle de una compra al ir hacia atrás lo que se espera es que te llevara al paso anterior a ese detalle de compra, es decir a los objetos que habías seleccionado para comprar, y en algunos casos estas famosas clases de navegación fallaban enviando al usuario a la pantalla principal de la aplicación o peor aún a otra pantalla que no tenía nada que ver con este flujo, esto suponía un problema.

Entra en escena el Navigation Component de Android

En el Google I/O de 2018 fue anunciada la creación de este componente para así tener un standard en el cual se basaran las aplicaciones Android, la versión estable de este component fue liberada hace aproximadamente un mes y actualmente existe una versión 1.0 que es compatible con AppCompat y una versión 2.0 que está disponible para Android Jetpack. Si quieres saber qué es Android Jetpack he hablado de él previamente en este articulo.

El uso de este componente está basado en crear un grafo en el cual estén conectados todos los activities y fragments de tu aplicación ademas de las "actions" que permiten cambiar de fragments.

Muéstrame el código

Para empezar a utilizar nuestro Navigation Component en nuestros proyectos es necesario agregar las siguientes dependencias a nuestro build.gradle del modulo app y del proyecto.

Java:

build.gradle del proyecto

buildscript {
        repositories {
            google()
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:3.3.1'
            //Pre Jetpack
            classpath 'android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0'
            // Jetpack
            //classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.1.0-alpha01"
        }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
Enter fullscreen mode Exit fullscreen mode

build.gradle del modulo app

 apply plugin: 'com.android.application'
//Java and Kotlin
apply plugin: 'androidx.navigation.safeargs'
//Only Kotlin
//apply plugin: "androidx.navigation.safeargs.kotlin"

android {
compileSdkVersion 28
defaultConfig {
    applicationId "com.example.navigationcomponent"
    minSdkVersion 21
    targetSdkVersion 28
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'

//Android Pre JetPack
def nav_version = "1.0.0"
implementation "android.arch.navigation:navigation-fragment:$nav_version"
// For Kotlin use navigation-fragment-ktx
implementation "android.arch.navigation:navigation-ui:$nav_version"
// For Kotlin use navigation-ui-ktx

//Android Jetpack
//    def nav_version = "2.1.0-alpha01"
//    implementation "androidx.navigation:navigation-fragment:$nav_version" // For Kotlin use navigation-fragment-ktx
//    implementation "androidx.navigation:navigation-ui:$nav_version" // For Kotlin use navigation-ui-ktx

implementation 'com.android.support:support-v4:28.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.android.support:recyclerview-v7:28.0.0'
}
Enter fullscreen mode Exit fullscreen mode

Kotlin:

Build.gradle del proyecto

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    ext.kotlin_version = '1.3.30'
    repositories {
        google()
        jcenter() 
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.3.1'
        //Pre Jetpack
        classpath 'android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        // Jetpack
        //classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.1.0-alpha01"
    }
}
allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
delete rootProject.buildDir
}
Enter fullscreen mode Exit fullscreen mode

Build.gradle del modulo app

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-android'
//Java and Kotlin
//apply plugin: 'androidx.navigation.safeargs'
//Only Kotlin
apply plugin: "androidx.navigation.safeargs.kotlin"
android {
compileSdkVersion 28
defaultConfig {
    applicationId "com.example.navigationcomponent"
    minSdkVersion 21
    targetSdkVersion 28
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
}
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'

    //Android Pre JetPack
    def nav_version = "1.0.0"
    implementation "android.arch.navigation:navigation-fragment:$nav_version"
    // For Kotlin use navigation-fragment-ktx
    implementation "android.arch.navigation:navigation-ui:$nav_version"
    // For Kotlin use navigation-ui-ktx

    //Android Jetpack
    //def nav_version = "2.1.0-alpha01"
    //implementation "androidx.navigation:navigation-fragment:$nav_version" // For Kotlin use navigation-fragment-ktx
    //implementation "androidx.navigation:navigation-ui:$nav_version" // For Kotlin use navigation-ui-ktx

    implementation 'com.android.support:support-v4:28.0.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'com.android.support:recyclerview-v7:28.0.0'
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    }

repositories {
    mavenCentral()
}
Enter fullscreen mode Exit fullscreen mode

1. Crear el Navigation Graph 

Para esto debemos abrir nuestra pestaña de Project en Android Studio y hacer click derecho en la carpeta "res", seleccionar Android Resource File, escribir el nombre del navigation graph que queremos y luego seleccionar Navigation.

2. Agregar el NavHostFragment en el layout de nuestro MainActivity.

Esto nos servirá para hospedar nuestros fragments. Para esto agregamos el siguiente código a nuestro layout.

<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"
tools:context=".MainActivity">

<fragment
    android:id="@+id/my_nav_host_fragment"
    android:name="androidx.navigation.fragment.NavHostFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:defaultNavHost="true"
    app:navGraph="@navigation/nav_graph" />
    </LinearLayout>
Enter fullscreen mode Exit fullscreen mode

Luego, hay que abrir nuestro archivo de navegación nav_graph.xml y seleccionar nuestro host fragment.

3. Agregar nuestros fragments al Navigation Graph

Basta hacer click en el icono de la pantalla con un signo + verde, luego hacer click en "Create new destination" desde ahí podremos agregar nuestros Fragments previamente creados o crear nuevos.

4. Conectar nuestros fragments utilizando actions

Para conectarlos basta hacer click en un fragment y "arrastrar" una flecha hacia nuestro segundo fragment. Y ya habríamos creado una acción.

5. Moverse entre fragments

Para movernos entre fragments utilizando el Navigation Component tenemos varias opciones. La principal siempre es obtener el NavigationController, dependiendo del contexto en el que te encuentres puedes realizarlo de una manera distinta.
En nuestro caso cambiaremos a otro fragment cuando se haga click en una imagen. Para esto utilizaremos el método de la clase Navigation FindNavController que acepta como parámetro una View o una Activity, luego utilizamos el método navigate y le pasamos como parámetro el id de nuestro action quedando de esta manera:

Java:

ivDog.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Navigation.findNavController(v).navigate(R.id.action_mainFragment_to_detailFragment);
        }
});
Enter fullscreen mode Exit fullscreen mode

Kotlin:

ivDog?.setOnClickListener {v -> Navigation.findNavController(v).navigate(R.id.action_mainFragment_to_detailFragment) }
Enter fullscreen mode Exit fullscreen mode

Pasar objetos de un fragment a otro

Para poder pasar objetos de un fragment solo tenemos que crear nuestro Bundle como se hacía tradicionalmente y agregarlo como parámetro al método .navigate() que vimos antes de esta forma:
Java:

ivGorilla.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Bundle bundle = new Bundle();
            bundle.putInt("id", R.drawable.animal_2);
            Navigation.findNavController(v).navigate(R.id.action_mainFragment_to_detailFragment, bundle);
        }
    });
Enter fullscreen mode Exit fullscreen mode

Kotlin:

ivGorilla?.setOnClickListener { v ->
        val bundle = Bundle()
        bundle.putInt("drawable_id", R.drawable.animal_2)
        Navigation.findNavController(v).navigate(R.id.action_mainFragment_to_detailFragment, bundle)
    }
Enter fullscreen mode Exit fullscreen mode

Para obtener el drawableId en nuestro fragment de detalles lo hacemos de la siguiente manera:

Java:

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    if (getArguments() != null) {
        int drawableId = DetailFragmentArgs.fromBundle(getArguments()).getDrawableId();
        ivAnimal.setImageResource(drawableId);
    }
}
Enter fullscreen mode Exit fullscreen mode

Kotlin:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    arguments?.let{
        //Without SafeArgs
        int drawableId = getArguments().getInt("drawable_id");
        ivAnimal?.setImageResource(drawableId)
    }
}
Enter fullscreen mode Exit fullscreen mode

Pasar objetos de un fragment a otro usando el plugin de safe-args

El Navigation Component utiliza un plugin de Gradle llamado safe-args que ayuda al momento de pasar objetos de un Fragment A a un Fragment B. Este ayuda a generar clases que se encargan de que los objetos sean type-safe.

Nota: Type-safe significa que el objeto es evaluado en tiempo de compilación y el compilador nos dará un error en caso de que intentemos pasar un tipo que sea erróneo.

Ademas de esto, nos ayuda a quitarnos el problema de estar asignando constantes como claves del bundle para luego usarlas y recuperar así los objetos.

Primero necesitamos ir a nuestro nav_graph.xml y hacer click en el fragment que va a recibir el objeto, en nuestro caso vamos a pasar el id del drawable que vamos a mostrar en nuestro Fragment de detalle. Así que hacemos click en el detailFragment, luego en el símbolo + que se encuentra a la derecha de "Arguments" y creamos nuestro nuevo argumento. Rellenamos los campos y hacemos click en "Add".

Necesitamos crear una instancia del objeto NavDirections, esta la obtenemos desde una clase que fue generada por el plugin de safeArgs. El plugin de safeArgs lo que hace es agregar un sufijo al nombre del fragment, en nuestro caso:
Si tu Fragment se llama MainFragment, esta clase se llamará MainFragmentDirections, luego utilizamos el método que nos provee y como podrás ver tiene como parámetro un entero que es el id del drawable que vamos a pasar al otro Fragment. Luego se llama al método que vimos antes de navigate() pero esta vez le vamos a pasar el NavDirections que hemos creado previamente.

Java:

   ivHorse.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            NavDirections mainFragmentDirections = MainFragmentDirections.actionMainFragmentToDetailFragment(R.drawable.animal_3);
            Navigation.findNavController(v).navigate(mainFragmentDirections);
        }
    });
Enter fullscreen mode Exit fullscreen mode

Kotlin:

 ivHorse?.setOnClickListener { v ->
        val mainFragmentDirections = MainFragmentDirections.actionMainFragmentToDetailFragment(R.drawable.animal_3)
        Navigation.findNavController(v).navigate(mainFragmentDirections)
    }
Enter fullscreen mode Exit fullscreen mode

Para obtener el drawableId que hemos pasado al fragment de DetailFragment lo hacemos de la siguiente forma:

Java:

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    if (getArguments() != null) {
        int drawableId = DetailFragmentArgs.fromBundle(getArguments()).getDrawableId();
        ivAnimal.setImageResource(drawableId);
    }
}
Enter fullscreen mode Exit fullscreen mode

Kotlin:

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
arguments?.let{
//Using SafeArgs
val drawableId = DetailFragmentArgs.fromBundle(it).drawableId
ivAnimal?.setImageResource(drawableId)
}
}
Enter fullscreen mode Exit fullscreen mode




Final

Esto es el uso básico del Navigation Component. También puede ser usado en el caso que quieras con un Drawer o con un Bottom Navigation View, pero eso da para otro artículo 😄.
Y con esto hemos terminado. En los próximos artículos hablaré un poco acerca de otros Android Architecture components. Si te ha gustado este artículo deja tu ❤️ y comenta si has tenido alguna duda al respecto, no olvides seguirme en twitter para que seamos amigos 😄 y date una vuelta por mi blog www.codingpizza.com en el caso de que quieras saber sobre otros tutoriales de Android.

💖 💪 🙅 🚩
gvetri
Giuseppe Vetri

Posted on May 1, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related