Pass data between destinations using Navigation in Android

This post is about how to pass data between destinations using Android Jetpack Navigation.

 

Use Safe Args to pass data

The Navigation component has a Gradle plugin called Safe Args that generates simple object and builder classes that enable type-safe navigation and argument passing between destinations.

Safe Args is strongly recommended for navigating and passing data, because it ensures type-safety.

Creating new project

1 . Create a new project by going to File ⇒ New Android Project, select Empty Activity, provide app name, select language to kotlin and then finally click on finish.

2 . Open app-level build.gradle file and under the dependencies section add the below libraries and then sync the project:

build.gradle

dependencies {
// jetpack navigation dependency
implementation("androidx.navigation:navigation-fragment-ktx:2.4.1")
implementation("androidx.navigation:navigation-ui-ktx:2.4.1")
}

3. To add Safe Args to your project, requires an additional plugin and one more classpath dependency:

  • Open app-level build.gradle file and add the below plugin under plugins section:
plugins {
id 'androidx.navigation.safeargs.kotlin'
}
  • Open project-level build.gradle file, include the following classpath, and then click on Sync Now:
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.4.1"

4. The below XML file shows a NavHostFragment as part of an app’s main activity:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>
  • android:nameattribute contains the class name of your NavHost implementation.
  • app:navGraph: defines which Navigation Graph will be associated with the Navigation Host.
  • app:defaultNavHost=”true”: ensures that the Navigation Host intercepts the system back button when pressed.

5. Let’s create two empty fragments i.e.  FragmentA and FragmentB which we will add to nav_graph as destinations.

6. Right-click on the res directory and choose New -> Android resource file. Set the title for the file and choose Navigation from the Resource type dropdown and then click on ok.

7. The below layout file defines the Navigation Graph.

nav_graph.xml

<?xml version="1.0" encoding="utf-8"?>
<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/nav_graph"
app:startDestination="@id/fragmentA">

<fragment
android:id="@+id/fragmentA"
android:name="com.c1ctech.passdatawithnavigation.FragmentA"
android:label="FragmentA"
tools:layout="@layout/fragment_a">

<action
android:id="@+id/action_fragmentA_to_fragmentB"
app:destination="@id/fragmentB" />

</fragment>

<fragment
android:id="@+id/fragmentB"
android:name="com.c1ctech.passdatawithnavigation.FragmentB"
android:label="FragmentB"
tools:layout="@layout/fragment_b">

<argument
android:name="data"
android:defaultValue="defaultValue"
app:argType="string" />

</fragment>
</navigation>
  • app:startDestinationdefines the starting destination. 
  • android:name: shows the name of the fragment that is associated with the destination.
  • android:label: contains the user-readable name of the destination.
  • tools:layout: defines the layout of the destination.
Add action and arguments

Action

In the navigation graph using actions connect both fragments from FragmentA to FragmentB, with a unique ID.

Arguments

FragmentB will receive data from FragmentA, so we have to add an argument to FragmentB.

In the navigation graph select FragmentB and from the right side window click the + button in Arguments to add one. Provide argument name, type, default value, and then click on Add.

After adding the argument, rebuild the project. Now inside the navigation-args folder you can see the generated classes using which we will pass data between destinations.

Creating Fragment’s layout file

8. The fragment_a.xml and fragment_b.xml define the layout file of Fragments.

fragment_a.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
tools:context=".FragmentA">

<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="FragmentA"
android:textSize="30sp"/>

<Button
android:id="@+id/btnGotoFragmentB"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
android:text="Goto FragmentB"
android:textAllCaps="false" />

</FrameLayout>

fragment_b.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
tools:context=".FragmentB">

<TextView
android:id="@+id/tvFragmentB"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="30sp" />

</FrameLayout>

Creating Fragment’s kotlin file

9. The FragmentA.kt and FragmentB.kt define the two fragments kotlin file.

Open FragmentA.kt file and add the below code:

FragmentA.kt 

package com.c1ctech.passdatawithnavigation

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.fragment.app.Fragment
import androidx.navigation.findNavController

class FragmentA : Fragment() {

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_a, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val navController = view.findNavController()
val button = view.findViewById<Button>(R.id.btnGotoFragmentB)
button.setOnClickListener {
val action = FragmentADirections.actionFragmentAToFragmentB("FragmentB")
navController.navigate(action)
}
}
}
  • The generated class FragmentADirections for FragmentA internally sets the value “FragmentB” to the argument of the name “data” we have created for FragmentB.

Open FragmentB.kt file and add the below code:

FragmentB.kt 

package com.c1ctech.passdatawithnavigation

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

class FragmentB : Fragment() {
private var DATA = ""

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val args = FragmentBArgs.fromBundle(requireArguments())
DATA = args.data
}

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_b, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val textView = view.findViewById<TextView>(R.id.tvFragmentB)
textView.text = DATA
}
}
  • Using the generated class FragmentBArgs for FragmentB, we will get the value stored for the data argument.

Writing MainActivity Code

10. The MainActivity file contains the below code:

MainActivity.kt

package com.c1ctech.passdatawithnavigation

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.navigation.NavController
import androidx.navigation.findNavController
import androidx.navigation.ui.NavigationUI.setupActionBarWithNavController

class MainActivity : AppCompatActivity() {

lateinit var navController: NavController

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

navController = this.findNavController(R.id.nav_host_fragment)

setupActionBarWithNavController(this, navController)
}

override fun onSupportNavigateUp(): Boolean {
navController.navigateUp()
return super.onSupportNavigateUp()
}
}

In the above code,

  • this.findNavController() gives us the instance of NavController.
  • setupActionBarWithNavController(): Sets up the ActionBar returned by AppCompatActivity.getSupportActionBar for use with a NavController.By calling this method, the title in the action bar will automatically be updated when the destination changes.
  • onSupportNavigateUp(): This method is called whenever the user chooses to navigate Up within your application’s activity hierarchy from the action bar.

When you run the app it will look like this:

         

 

Leave a Reply