iOS App Distribution & Best Practices — Available Now!

Frustrated with the App Store and deployment best practices? You're not alone!
Get the best book on managing certificates, TestFlight, automation and more.
Start reading for free — today!

Home Android & Kotlin Tutorials

Using Safe Args With the Android Navigation Component

In this Safe Args tutorial, you’ll learn how to safely pass data between different destinations while using the Android Navigation Component.

5/5 3 Ratings

Version

  • Kotlin 1.4, Android 10.0, Android Studio 4.2

Passing data between screens is a common use case in an Android app. This enables destinations to communicate with each other and builds a continuous flow inside an app. Google recommends using the Android Navigation Component to manage navigation between destinations in your app. In this tutorial, you’ll learn to use this component with Safe Args to pass data while navigating between screens.

You’ll build SafeFly, an app that lets you buy plane tickets. The first screen retrieves the user’s travel information, then passes it to a confirmation screen. During the process, you’ll learn:

  • Why you should use Safe Args.
  • How to pass data using Safe Args.
  • Things to consider when using Safe Args and code shrinking in the same app.
Note: This tutorial assumes you have previous experience using the Android Navigation Component. If you don’t, check out our Navigation Architecture Component tutorial.

Getting Started

Download the materials using the Download Materials button at the top or bottom of this tutorial. Open Android Studio and import the starter project.

Take a moment to familiarize yourself with the code. You’ll see the following classes:

  • TravelInformationFragment.kt: Fragment where the user enters their information.
  • ConfirmationFragment.kt: Fragment that displays the user’s travel information to confirm it.
  • TravelerInformation.kt: Data class that wraps personal user information required for purchasing a plane ticket.

Build and run the project. You’ll see a screen that allows the user to enter their information, which travel add-ons they want and a promo code, if they have one. On the next screen, you’ll notice the fields are all empty. The user’s information should display instead. That’s what you’ll handle next.

Starter project

Why Safe Args?

Before the introduction of Safe Args, passing data when navigating to a different screen required Bundles. The first screen, the sender, would build a Bundle instance and populate it with data. The second screen, the receiver, would later retrieve this data. This manual approach of sending and unwrapping data is unreliable, as the sender and receiver must agree on:

  • The keys.
  • The default values for each key.
  • The type of data corresponding to the keys.

There’s also no way for the receiver to force the sender to pass all the required data. Furthermore, when unwrapping the data, type safety isn’t guaranteed on the receiver’s end.

Android introduced Safe Args to resolve these issues. Safe Args is a Gradle plug-in that generates code to add data to a Bundle and get it back in a simple and type-safe manner.

Android passing data back and forth with arrows.

Now that you see why Safe Args is useful for your project, it’s time to implement it.

Note: Safe Args only works with Gradle, which means that you can’t use it if your projects use a different build tool.

Adding Safe Args to Your App

To add Safe Args to your project, include the following classpath in your top-level build.gradle:

dependencies {
...
  classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.2"
}

Apply Safe Args’s Kotlin plug-in by adding the following line to your app-level build.gradle at the top:

apply plugin: "androidx.navigation.safeargs.kotlin"

The plug-in above generates Kotlin code, so you should use it in Kotlin-only modules, such as in this tutorial.

If your module uses Java or a mix of Java and Kotlin, apply the following plug-in, which generates Java code:

apply plugin: "androidx.navigation.safeargs"

Click Sync now and wait for Gradle to sync the dependencies.

Passing Data With Safe Args

The data you pass from one destination to another is called an argument. This data can be a simple number or a complex model. Note that passing the minimal amount of data necessary between destinations is a best practice, because there’s a limit to the total space available for all saved states.

An argument can be of any type that Bundle supports, including:

  • Integer
  • Float
  • Long
  • Boolean
  • String
  • resource reference
  • Parcelable
  • Serializable
  • Enum

Primitive types — int, float, long and bool — back Integer, Float, Long and Boolean respectively on the JVM, so they can’t be null. In contrast, String, Parcelable and Serializable can accept null values.

You can provide a default value to an argument, which it will take if the sender doesn’t assign a value at runtime.

An argument can also be an array of any of the above types, except resource references and Enums. If an argument is an array, it can have a null value, regardless of its underlying type. Additionally, it can only have a null default value.

Defining an Argument in a Destination

The confirmation screen in the app expects to receive certain information: the traveler’s information, travel add-ons and promo code. The navigation graph should define these three arguments. In this section, you’ll see how to add arguments using the editor.

Open navigation_graph.xml, select the Design window at the top-right corner and click ConfirmationFragment.

Adding Safe Args in the navigation editor

On the right, you’ll notice the Arguments tab, which has a + button to its right. Click the button to add the first argument, the promo code. In the Add Argument dialog, do the following:

  1. Set its name to promoCode.
  2. Select its type to String.
  3. Set it to Nullable.
  4. Set its default value to @null since the argument is optional.

Click Add. Congrats, you just created your first argument!

Defining Safe Args

Now, switch to the Code window by selecting it at the top-right corner and notice that inside ConfirmationFragment, a new argument appears:

<argument
  android:name="promoCode"
  app:argType="string"
  app:nullable="true"
  android:defaultValue="@null" />

That’s quite readable and straightforward. :]

Switch back to the Design window and add the add-ons argument. Call it travelAddOns, set its type to Integer since it should contain multiple values and set it to be an array. Once added, there should be a new argument as follows:

<argument
  android:name="travelAddOns"
  app:argType="integer[]" />

There goes your second argument!

Adding a Custom Type Argument

It’s time to create the last argument for the traveler’s information, but what would its type be? It should be of type TravelerInformation, which is a data class that’s in the project. Remember that to pass it with Safe Args, it has to be one of the supported types. You’ll have to make TravelerInformation a Parcelable. To do this, add the following plug-in to the app-level build.gradle:

apply plugin: "kotlin-parcelize"

After syncing the project, open TravelerInformation.kt and make the class a Parcelable. Annotate it with @Parcelize and have it implement the Parcelable interface. The class should now be as follows:

@Parcelize
data class TravelerInformation(
    val fullName: String,
    val age: Int,
    val passportNumber: String
): Parcelable

In the code above, adding @Parcelize generates the code required to make TravelerInformation parcelable.

You’re now ready to set TravelerInformation as an argument type.

Go back to navigation_graph.xml and create the last argument for the traveler’s information. Call it travelerInformation, select the argument type custom parcelable, select the class TravelerInformation and click Add. Build the project and let Safe Args do its magic.

Retrieving Arguments in a Destination

Since you’ve defined arguments for the ConfirmationFragment destination, Safe Args will generate a class named ConfirmationFragmentArgs, whose name is the destination ConfirmationFragment and the suffix Args. This class has a static method fromBundle(Bundle). You’ll use it to retrieve the arguments ConfirmationFragment needs.

To do this, open ConfirmationFragment.kt and replace the TODO on line 64 with the following:

// 1
val bundle = arguments
if (bundle == null) {
  Log.e("Confirmation", "ConfirmationFragment did not receive traveler information")
  return
}

// 2
val args = ConfirmationFragmentArgs.fromBundle(bundle)
showTravelerInformation(args.travelerInformation)
showTravelAddOns(args.travelAddOns)
showPromoCode(args.promoCode)

In the code above, you’re:

  1. Getting the fragment’s arguments, assigning them to the variable bundle and making sure it isn’t null. If it is, the program logs the error and returns from the method.
  2. Using ConfirmationFragmentArgs.fromBundle(), which Safe Args generated. You pass it the non-null arguments this Fragment received, then retrieve each piece of information it expects: the traveler’s information, the travel add-ons and the promo code.

Build and run the app. You’ll notice that the app crashed on clicking Next. This is because the confirmation screen expects data you haven’t sent yet. You’ll wire both screens in the next steps!

The app crashed, a cartoon monster looks surprised!

Defining an Argument in an Action

Just as the confirmation screen specifies the arguments it expects to receive, the action that navigates from the information screen to the confirmation screen has to define those same arguments. This ensures the sender — the travel information screen — is sending the correct information.

In the previous step, you added arguments using the editor. In this step, you’ll add them using XML, which is just as easy!

You’ll again need to define the three pieces of information to send: the traveler’s information, the travel add-ons and the promo code. Open navigation_graph.xml, select the Code window from the top-right corner and scroll down to the action block.

Start by defining the promo code argument using the code below:

<action ...>
  <argument
    android:name="promoCode"
    android:defaultValue="@null"
    app:argType="string"
    app:nullable="true" />
</action>

The argument definition above specifies:

  1. The argument’s name.
  2. The argument’s default value. Since the promo code is optional, you set it to null in case the user doesn’t enter one.
  3. The argument’s type.
  4. The argument’s nullability. If the traveler doesn’t have a promo code, it appears as null.

This is the exact definition of the promo code argument inside ConfirmationFragment.

As an exercise, add the remaining two arguments: the traveler’s information and the add-ons. If you get stuck, check how they’re defined inside ConfirmationFragment.

Need help? Just open the spoiler below to find out how.

[spoiler title=”Solution”]
Here are the arguments to add below the previous one:

<argument
  android:name="travelAddOns"
  app:argType="integer[]" />

<argument
  android:name="travelerInformation"
app:argType="com.raywenderlich.android.safeargs.TravelerInformation" />

[/spoiler]

Once you’re done, build and run to let Safe Args do its magic yet again.

The app crashed again, a cartoon monster looks confused.

Sending Arguments From a Destination

This time, Safe Args will generate the class TravelInformationFragmentDirections, whose name is the initial destination TravelInformationFragment with the suffix Directions. Inside this class is actionTravelInformationFragmentToConfirmationFragment, which is the name of the navigation action defined in navigation_graph.xml. This method takes all the mandatory data it will pass to the confirmation screen. Note that mandatory arguments are those that don’t have a default value.

Open TravelInformationFragment.kt and replace the TODO on line 71 with the following:

// 1
val travelerInformation = getTravelerInformation()
val addOns = getAddOns(clickListener)
val promoCode = getPromoCode()

// 2
val directions = TravelInformationFragmentDirections.actionTravelInformationFragmentToConfirmationFragment(promoCode, addOns, travelerInformation)

Here’s what’s happening in the code above:

  1. You read the information the user entered on the screen, which you must pass to the next screen. This includes their traveler information, the travel add-ons they selected and their promo code.
  2. You build a NavDirections instance that wraps the arguments passed during navigation. Note that the order of arguments has to match the order in navigation_graph.xml.

Now, change the argument of findNavController().navigate() from R.id.action_travelInformationFragment_to_confirmationFragment to directions. The method invocation should now appear as follows:

findNavController().navigate(directions)

In the code above, you use NavDirections, which you created earlier, to specify the destination and the arguments to pass.

Build and run. Enter data on the first screen, select a couple of add-ons, optionally input a promo code and navigate to the following screen. You’ll see the information you’ve entered display correctly. The app’s finally functional!

Final working project

Safe Args and Proguard

When using Parcelable, Seriazable and Enum types with Safe Args, you specify the type’s name in the navigation graph as you define the argument. The mapping between these types and their corresponding classes in your project won’t persist if your project uses obfuscation. Therefore, you must prevent the minification process at build time from obfuscating these classes.

SafeFly uses obfuscation and code shrinking, as you may have noticed in build.gradle.

buildTypes {
  release {
    minifyEnabled true // Enables code shrinking and obfuscation
    ...
  }
}

It passes a TravelerInformation instance when navigating between screens in the app. There are two approaches you can use to prevent obfuscation from occurring. The first is annotating the class with @androidx.annotation.Keep in TravelerInformation.kt:

@Keep
class TravelerInformation(...): Parcelable

The second is adding keepnames rules to proguard-rules.pro:

-keepnames class com.raywenderlich.android.safeargs.TravelerInformation

Implement either of these options, go to the bottom-left corner of Android Studio, click Build Variants and select release from Active Build Variant.

Using android studio build variants menu to select the release instead of build.

Build and run. This runs a release build with code that was shrunk and obfuscated. Make sure you can still enter information on the first screen and see it displayed on the confirmation screen.

Final working project

Where to Go From Here?

Download the final project using the Download Materials button at the top or bottom of the tutorial.

Congratulations! You learned why you should use Safe Args with Android’s Navigation Component. You also saw how to create arguments and pass data between destinations. Additionally, you learned how to properly handle using Safe Args and obfuscation in your app.

If you need to brush up on Android’s ‘Navigation Component or want to learn about advanced topics such as deep links and transitions, check out The Navigation Architecture Component Tutorial series of articles.

I hope you enjoyed this tutorial! If you have any questions or comments, please join the forum discussion below.

Average Rating

5/5

Add a rating for this content

3 ratings

More like this

Contributors

Comments