Passing Data Between Fragments with SafeArgs in Android

Efficiently pass data between fragments in Android Navigation Component using SafeArgs. Follow our tutorial for a step-by-step example.

by

Passing data between fragments using android navigation SafeArgs example

Passing data between fragments is a common task in Android development. With the introduction of the Navigation Component in Android Jetpack, passing data between fragments has become easier and more efficient.

In this article, we will learn how to pass data between fragments using SafeArgs in Kotlin.

SafeArgs is a Gradle plugin that generates a class for each fragment with arguments that can be used to pass data between fragments. SafeArgs helps in type-safe argument passing and reduces the chances of errors while passing data between fragments.

Let’s get started by creating a new project in Android Studio and adding the Navigation Component to the project.

Setting Up Project

Open Android Studio and create a new project. Give the project name SafeArgsDemo.

How to Create a New Project in Android Studio

After creating the project in Android Studio, add the Navigation Component to the project. To add the Navigation Component, add the following dependencies in the app-level build.gradle file.

Adding Dependencies

dependencies {
    def nav_version = "2.3.5"

    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}

SafeArgs plugin is included in the Navigation Component library and can be added to your project by adding the following dependency in your top-level build.gradle file:

Android safe-args dependency

buildscript {
    repositories {
        google()
    }
    dependencies {
        def nav_version = "2.5.3"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}

And add the following plugin to your app-level build.gradle file.

plugins {
  id 'androidx.navigation.safeargs.kotlin'
}

Create two fragments, FragmentA and FragmentB, and add them to the navigation graph.

Creating Navigation Graph

To create a navigation graph, create a new XML file named navigation.xml in the res folder of the project. Then, add the following code to the navigation.xml file.

<?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/navigation"
    app:startDestination="@id/fragment_a">

    <fragment
        android:id="@+id/fragment_a"
        android:name="com.example.safeargsdemo.FragmentA"
        tools:layout="@layout/fragment_a">

        <action
            android:id="@+id/action_fragment_a_to_fragment_b"
            app:destination="@id/fragment_b"
            app:popUpToInclusive="true" />
    </fragment>

    <fragment
        android:id="@+id/fragment_b"
        android:name="com.example.safeargsdemo.FragmentB"
        tools:layout="@layout/fragment_b">

        <argument
            android:name="msg"
            app:argType="string" />
    </fragment>
</navigation>

In the above code, we have added two fragments, FragmentA and FragmentB, to the navigation graph. The start destination of the graph is FragmentA.

In the above code, we have added an argument named msg of type string to our FragmentB. This argument will be used to pass data between fragments using SafeArgs.

The argument can be accessed in your code using the FragmentBArgs class, which is automatically generated by the SafeArgs plugin.

In FragmentA, add a button and navigate to FragmentB on the button click.

Creating XML Layouts

Below are the layout XML files for FragmentA, FragmentB, and MainActivity. These files define the user interface for each component of the application

FragmentA Layout XML

This file defines the layout for FragmentA. In the following code, we have created an EditText and a Button. When the user enters some text into the EditText and clicks the Button, FragmentB will open and display the data that was entered into the EditText.

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 
    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=".FragmentA">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:elevation="0dp"
        android:fitsSystemWindows="true"
        app:liftOnScroll="false">

        <com.google.android.material.appbar.MaterialToolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:title="FragmentA" />
    </com.google.android.material.appbar.AppBarLayout>

    <androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">

        <androidx.appcompat.widget.LinearLayoutCompat
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="24dp"
            android:orientation="vertical">

            <com.google.android.material.textfield.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Enter Message">

             <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/data_passed"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:inputType="textPersonName|textMultiLine" />
            </com.google.android.material.textfield.TextInputLayout>

            <Button
                android:id="@+id/send_data"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginVertical="24dp"
                android:text="Send to FragmentB" />
        </androidx.appcompat.widget.LinearLayoutCompat>
    </androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

FragmentB Layout XML

In the following code, we have created a TextView. When the user clicks the Button in Fragment A, Fragment B will open and display the data in the TextView.

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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=".FragmentB">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:elevation="0dp"
        android:fitsSystemWindows="true"
        app:liftOnScroll="false">

        <com.google.android.material.appbar.MaterialToolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:navigationIcon="@drawable/round_arrow_back_24"
            app:title="FragmentB" />
    </com.google.android.material.appbar.AppBarLayout>

    <TextView
        android:id="@+id/data_passed"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="Passed data"
        android:textAppearance="?attr/textAppearanceTitleLarge"
        app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

MainActivity Layout XML

This file defines the layout for the MainActivity. It contains a FragmentContainerView that serves as the container for both FragmentA and FragmentB.

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

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragment_host_main"
        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/navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>

Writing Kotlin Code

Now, let’s add functionality to pass data from FragmentA to FragmentB.

FragmentA.kt

package com.example.safeargsdemo

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import com.example.safeargsdemo.databinding.FragmentABinding

class FragmentA : Fragment() {
    private var _binding: FragmentABinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        // Inflate the layout for this fragment
        _binding = FragmentABinding.inflate(layoutInflater)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // Passing data on button click
        binding.sendData.setOnClickListener {
            // Getting data from edittext
            val dataEditText = binding.dataPassed.text.toString()

            // Sending the data to FragmentB using safeargs
            val direction = FragmentADirections.actionFragmentAToFragmentB(dataEditText)
            findNavController().navigate(direction)
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

FragmentB.kt

In FragmentB, retrieve the data passed from FragmentA using SafeArgs.

package com.example.safeargsdemo

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.example.safeargsdemo.databinding.FragmentBBinding

class FragmentB : Fragment() {
    private var _binding: FragmentBBinding? = null
    private val binding get() = _binding!!
    private val args: FragmentBArgs by navArgs()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        // Inflate the layout for this fragment
        _binding = FragmentBBinding.inflate(layoutInflater)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // Getting data
        val data = args.msg

        // Setting the data in textview
        binding.dataPassed.text = data

        // Closing the fragment on back press
        binding.toolbar.setNavigationOnClickListener {
            findNavController().popBackStack()
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

In the above code, we have retrieved the data passed from FragmentA using SafeArgs. We have used the navArgs() delegate to retrieve the data. navArgs() is a delegate that provides type-safe access to the arguments passed to the fragment.

Testing the App

Build the project and run it. Click on the button in FragmentA, and you will see the data “Hey, I am from FragmentA” displayed in FragmentB.

Android Navigation SafeArgs screenshot
Android Navigation SafeArgs Demo

That’s it! We have successfully passed data between fragments using SafeArgs in Kotlin.

Benefits of SafeArgs

SafeArgs provides several benefits, including:

Type-safety: SafeArgs generates a class for each fragment with arguments that can be used to pass data between fragments. This ensures that the data passed between fragments is of the correct type and reduces the chances of runtime errors.

Efficiency: SafeArgs generates code that is optimized for performance, making it more efficient than other methods of passing data between fragments.

Error-free: Since SafeArgs generates code at compile-time, it reduces the chances of errors that may occur while passing data between fragments.

In conclusion, SafeArgs provides a type-safe way to pass data between fragments in the Navigation Component.

It eliminates the need for writing boilerplate code and reduces the chances of errors while passing data between fragments.

With SafeArgs, passing data between fragments has become easier and more efficient.

I hope this article helps you understand how to pass data between fragments in the navigation component using Android SafeArgs.

So, go ahead and try it out today!

Popular Posts