How is safe args in navigation component type safe (Android)?

314 Views Asked by At

In a normal fragment transaction we would pass data as:

Fragment fragment = new Fragment();
Bundle bundle = new Bundle();
bundle.putInt(key, value);
fragment.setArguments(bundle);

Isn't this type safe too? So what does it mean when we say that safe args are type safe? What exactly does type safety mean when we say that safe args are type safe?

Thanks in advance!

2

There are 2 best solutions below

4
Mikhail Guliaev On BEST ANSWER

Yes, you provided the regular form of passing arguments between fragments. This is type safe because Bundle class provide API to put and get data of different types. It means that you will not encounter ClassCastException using it (see detailed explanation below)

So what does it mean when we say that safe args are type safe?

I presume you're talking about Safe args, which is a Gradle plugin for Android that provides a type-safe and easy-to-use mechanism for passing data between destinations in the Android Navigation component.

With Safe Args, you define the arguments for each destination in an XML file, and the plugin generates a strongly-typed class for each destination that contains accessor methods for each argument. These classes help to ensure that the arguments are of the correct type and prevent runtime errors caused by incorrect argument values. That makes this way of passing type safe and you can use it when you're using Android Navigation component.

So you can define your fragments like this:

<fragment
    android:id="@+id/destination_fragment"
    android:name="packageName.DestinationFragment">

    <argument
        android:name="firstArg"
        app:argType="integer"
        android:defaultValue="0" />

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

</fragment>

And start this fragment, passing arguments with Safe Args:

val action = FragmentDirections.actionSourceFragmentToDestinationFragment(firstArg = 12345, secondArg = "Hello World!")
findNavController().navigate(action)

Update

When you use the standard way of passing objects between fragments, it is not being checked on the compile time. So for instance, if you put Int value in a Bundle and try to get a String with a same key it will return the default value.

For example the value of value variable will be null in the example below:

    val bundle = Bundle().apply {
        putInt("key", 1)
    }
    
    val value = bundle.getString("key")
    
    println(value) // null!

You can see why it happens in a BaseBundle.getString() method:

@Nullable
public String getString(@Nullable String key) { // key = "hey"
    unparcel();
    final Object o = mMap.get(key); // Here we got Integer = 1
    try {
        return (String) o; // Trying to cast Integer to String
    } catch (ClassCastException e) {
        typeWarning(key, o, "String", e);
        return null; // ClassCastException was caught => return null!
    }
}
0
Jintin On

Regarding to type safe Bundle, I recently build a tool to achieve that.

Leverage on some Kotlin feature (value class, extension function, etc), we can easily use Bundle in a much safer way.

Example usage:

val textKey = StringKey("SomeStringKey")
val intKey = IntKey("SomeIntKey")
val bundle = Bundle()

bundle.put(textKey, "Some text")   // only String value is acceptable
bundle.put(intKey, 12345)          // only Int value is acceptable

val text = bundle.get(textKey, "") // guarantee result to be a String  
val int = bundle.get(intKey, -1)   // guarantee result to be an Int

The concept is we associate type info in the key string like StringKey, IntKey where it's a value class in Kotlin will compile back to String in the end but able to provide type information at build time. And the extension function get and set will check these info to ensure everything is matched.

github link : https://github.com/Jintin/TypedBundle