Search code examples
androidandroid-architecture-navigationandroid-bundleandroid-safe-args

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


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!


Solution

  • 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!
        }
    }