Using Safe args plugin — current state of affairs
Couple of days ago I started working with the new Navigation Component that was presented at this years Google I/O and is part of Android Jetpack. While reading about it in official documentation, I stumbled upon a section about passing data between destinations using safe args Gradle plugin. As documentation states, safe args plugin
generates simple object and builder classes for type-safe access to arguments specified for destinations and actions. Safe args is built on top of the Bundle.
So I thought I’ll give it a try and share my findings. I’ll show you how to pass data using this plugin and also what types of data is supported.
❗️ Note: At the time of writing most recent release of safe args Gradle plugin was alpha04. I’ll try to update this post when things change in future.
Type-safe data passing
Passing data using safe args plugin takes only a few steps. First you have to add a dependency to your project level gradle file:
classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha04"
and apply plugin in module level gradle file:
apply plugin: "androidx.navigation.safeargs"
Then you define the argument for target destination in your navigation graph (we assume you already defined at least two destinations in the graph).
This requires three parameters: argument name, type and default value. You can use right panel in design tab of navigation editor for that.
❗️ Note: When you select argument type it will generate this xml code: app:type=”string”
. If you try to rebuild project, Android Studio will give you an error:
The ‘type’ attribute used by argument ‘title’ is deprecated. Please change all instances of ‘type’ in navigation resources to ‘argType’.
It’s self explanatory enough so you know that you should change it to app:argType=”string”
manually in xml. It’s because in plugin alpha04 release they’ve changed it. There’s already an issue on issue tracker for this so it should be fixed in upcoming releases.
When you’re done, switch to text tab and you should see something like this in your destination:
<fragment ...>
<argument
android:name="title"
android:defaultValue="null"
app:argType="string" />
</fragment>
Rebuild project so that two necessary builder classes get generated. These java classes are then used to pass the value of the argument.
First class is used for inserting data. It has name after the direction from which you send data and ends with ‘Directions’ e.g. HomeFragmentDirections. Second one is used to retrieve data at target direction and its name ends with ‘Args’ e.g. DetailFragmentArgs.
If you look at the generated classes, you’ll see they’re using Bundle and properties matching the arguments defined in our xml. We can access these properties through getters and setters.
To send data from destination you need to obtain an instance of generated class which is named after action that you created for navigating to another destination (in this example it’s called ActionDetail). After that just insert your data using a setter method and pass actionDetail instance as a parameter in your NavController.navigate call.
val actionDetail = HomeFragmentDirections.ActionDetail()
actionDetail.setTitle(item)
Navigation.findNavController(view).navigate(actionDetail)
Receiving data in target destination depends on it being an activity or fragment. If you’re retrieving data in activity you would get the bundled data from extras in onCreate method:
val username = MainActivityArgs.fromBundle(intent?.extras).username
For retrieving data in fragment use arguments property in either onCreate, onCreateView or onViewCreated lifecycle methods:
arguments?.let {
val safeArgs = DetailFragmentArgs.fromBundle(it)
val title = safeArgs.title
}
And that’s all. You’ve successfully transferred your data using safe args.
Supported data types
When you add arguments using design tab of navigation editor it lets you pick from only three types: string, integer and reference.
But we come from the Bundle world where we could pass all kinds of primitive types and also Parcelable objects. So I was curious and tried to change value of app:argType
to boolean manually in xml.
It worked without problems. Then I found this issue on issue tracker where people asked for support for more types. So it looks like they’ve already done it for boolean, long and float. Using char and double as an argument type didn’t work though. Compile error was: part ‘double’ is keyword.
Then I tried passing a reference. Reference takes integer value so I thought it was for passing resource ids and my assumption was quickly confirmed by comment I found in the same issue:
An argument with a type of “reference” is a reference to an Android resource i.e., with values like @string/value in XML or R.string.value in code.
There I’ve also learned that since safe args plugin alpha03 (I was using alpha04 at the time) it also supports Parcelable.
You should use @null
as default value and you have to also add app:nullable=”true”
to allow nullable types because arguments are considered non-null by default and Android Studio will give you an error.
Also on page with release notes was mentioned you should use a fully qualified class name for app:argType
. So I tried following:
<argument
android:name=”item”
android:defaultValue=”@null”
app:nullable=”true”
app:argType=”com.vepe.navigation.model.Item” />
Where Item is a simple data class in my model layer implementing Parcelable interface. If you’re not familiar with @Parcelize
annotation, look here.
@Parcelize
data class Item(val index: Int, val title: String, val value: Double): Parcelable
❗️ Note: You have to use a full class name. Using app:argType=”Item”
will result in compile error because generated classes won’t be able to find and import your class.
Conclusion
It looks like safe args plugin currently supports more types than Android Studio 3.2 lets you know about. That’s understandable of course, since the studio is still in beta (beta5 at the time of writing). I guess they’ll update the Navigation editor UI to match this in future release.
But it’s nice to know that we’re already able to pass values of type long, float, boolean and also Parcelable objects and not just integer, string and reference.
📢 Update: Since the release of plugin version 1.0.0-alpha08 you can now pass also Serializable objects, enum values and arrays of primitive types and Parcelables.
enum class Category {
CLOTHES, SHOES, ACCESSORIES, BAGS, UNDEFINED
}
You can specify the default value to be used directly from your enum class:
<argument
android:name="category"
android:defaultValue="UNDEFINED"
app:argType="com.vepe.navigation.model.Category"/>
For passing arrays of values define argument type for primitive types as follows:
app:argType="integer[]"
And similarly for arrays of Parcelables:
app:argType="com.vepe.navigation.model.Item[]"
📢📢 2nd update: Most recent release of plugin version 1.0.0-alpha10 (and 1.0.0-alpha11, which is a hotfix release for the previous one) bring Kotlin users nice ways to write less code while using safe args plugin.
From now on Kotlin users can lazily get arguments using by navArgs()
property delegate. For e.g. in the past I accessed username in my activity using fromBundle method.
username = MainActivityArgs.fromBundle(intent?.extras).username
Using mentioned property delegate and specifying expected Args type you can now retrieve your arguments during variable declaration:
private val mainActivityArgs by navArgs<MainActivityArgs>()
// later in code access properties from args
username = mainActivityArgs.username
Other nice improvement is you can generate Kotlin code by applying plugin 'androidx.navigation.safeargs.kotlin'
instead of 'androidx.navigation.safeargs'
in your app or other module gradle file.
There are some breaking changes related to this. Generated Direction
and NavDirections
classes (such as my ActionDetail class) no longer have a public constructor. Because of that you can no longer pass data through safe args the old way:
HomeFragmentDirections.ActionDetail().setItem(item)
You should only be interacting with the generated static methods. Name of method matches name of the action class. For the previous example this means using generated actionDetail()
method which takes item as an argument:
HomeFragmentDirections.actionDetail(item)
For the complete list of new features and changes look here. You can find up-to-date examples of using safe args in my navigation repo.
That’s all for now. I hope you find information in this post useful. If you have any suggestions or questions, feel free to leave a comment below. 😉
Also this is my first post so every single clap will be really appreciated. 👏❤️