6

I added a nullable argument to my start destination:

<?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"
        android:id="@+id/nav_graph"
        app:startDestination="@id/startDest">

    <fragment android:id="@+id/startDest"
          android:name="com.myapp.MyStartFragment"
          android:label="Start"
          tools:layout="@layout/fragment_start">
        <argument
            android:name="dataObject"
            app:argType="com.myapp.MyDataObject"
            android:defaultValue="@null"
            app:nullable="true"/>
        ...
    </fragment>
    ...
</navigation>

But when I load my app, I get the following exception:

java.lang.IllegalStateException: Fragment MyStartFragment{a4ffd1f (ca52d4dc-ff36-4a93-8ebf-f11af7b7d5aa) id=0x7f080145} has null arguments
    at com.myapp.MyStartFragment$$special$$inlined$navArgs$1.invoke(FragmentNavArgsLazy.kt:42)
    at com.myapp.MyStartFragment$$special$$inlined$navArgs$1.invoke(Unknown Source:0)
    at androidx.navigation.NavArgsLazy.getValue(NavArgsLazy.kt:44)
    at androidx.navigation.NavArgsLazy.getValue(NavArgsLazy.kt:34)
    at com.myapp.MyStartFragment.getArgs(Unknown Source:27)
    at com.myapp.MyStartFragment.onAttach(MyStartFragment.kt:85)

And the exception is triggered by this piece of code in MyStartFragment:

private val args: MyStartFragmentArgs by navArgs()
override fun onAttach(context: Context) {
    super.onAttach(context)
    val title = if(this.args.dataObject == null) getString(R.string.start_list_title) else this.args.dataObject!!.name
    ...
}

And here is the code for MyDataObject:

@Parcelize
data class MyDataObject (
    val id: String,
    val name: String,
    val externalIdentifier: String
    val type: MyDataEnumType,
    var responsibleUser: SomeOtherParcelableClass?
): Parcelable 

What I don't understand is that my start destination doesn't get passed arguments properly by the navigation controller. Am I missing something here?

Sebastien
  • 3,583
  • 4
  • 43
  • 82
  • Could you have a look into possibilities to attach `MyStartFragment` manually? From your code you posted it should work. – tynn Aug 21 '19 at 17:16
  • What do you mean by attaching it manually? – Sebastien Aug 21 '19 at 18:18
  • The exception states that `arguments` is null. Since navigation components takes care of setting these, there might be a place where you create the fragment differently. Calling the constructor without setting any arguments or some configuration you omitted. – tynn Aug 21 '19 at 18:22
  • But that's the thing: the navigation component is supposed to set those arguments, and it does so in other fragments where I navigate after using actions. But for some reason, either because of a bug in the navigation component, or indeed a missing configuration, it fails to do it in this case. – Sebastien Aug 21 '19 at 18:30
  • Have you tried an unstable version of the library? I implemented your example with version `2.1.0-rc01` and everything worked fine. – tynn Aug 21 '19 at 19:24
  • Can you post you `MyDataObject ` class? – BlackHatSamurai Aug 21 '19 at 21:17
  • @tynn I just tried to use the latest version of the navigation component (2.2.0-alpha01) but now I have compilation errors everywhere I try to use navArgs(): Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option. And yet I set sourceCompatibility and targetCompatibility to 1.8 in my compileOptions. – Sebastien Aug 21 '19 at 21:34
  • @BlackHatSamurai I added it to my question – Sebastien Aug 21 '19 at 21:38
  • Can you post the Fragment you are trying to pass the data to @Sebastien – BlackHatSamurai Aug 21 '19 at 21:55
  • The fragment I'm trying to pass the data to is MyStartFragment. It is both a start destination and a target destination for an action that comes from another fragment. When it is loaded as a start destination, the argument should be null. And when it is loaded from another fragment, this other fragment will pass a non-null value into the argument. – Sebastien Aug 21 '19 at 23:03

2 Answers2

7

Hello I am assuming you want to achieve something like below

BeforeFragment --arg--> StartFragment --> AfterFragment

sample nav graph

This flows are similar first time user, returning user flows. Here BeforeFragment is last fragment in login_nav_graph nested graph. StartFragment is the starting destination of main_nav_graph. StartFragment is the first screen returning user sees.

So in BeforeFragment you may set args as follows

val userJohn:User = User(34, "John", 645, UserType.TYPE2, Guardian("Mike"))
val action = BeforeFragmentDirections.actionGlobalStart(userJohn)
findNavController().navigate(action)

and in StartFragment you may do following as you already did

title = if(this.args.user == null)
   getString(R.string.user_name) // mocks loading saved user name
else
   this.args.user?.name // when user is first time user read from passed args

Sample Repo can be found here


My best guess

This issue is due to bug in older navigation version, so use 2.2.0-alpha01 which is I am using in the sample repo.

In order to fix errors that occurs when moved to new navigation version in you module gradle file add following

android {
    ...
    kotlinOptions {
        jvmTarget = "1.8" // set your Java version here
    }
}

This fixes the error

Cannot inline byte code ...


Keep in mind passing complex objects as arguments is not recommended. Quoting from docs.

In general, you should strongly prefer passing only the minimal amount of data between destinations. For example, you should pass a key to retrieve an object rather than passing the object itself, as the total space for all saved states is limited on Android. If you need to pass large amounts of data, consider using a ViewModel as described in Share data between fragments.

If this fixes your issue please confirm the answer, since I spent lot of time preparing this post.

user158
  • 12,852
  • 7
  • 62
  • 94
  • Thanks a lot. That fixed my issue. – Sebastien Aug 23 '19 at 18:29
  • I was using version 2.0.0 for passing a nullable string as argument and I was getting the same complaint as in the title. After updating to 2.1.0 and adding the kotlinOptions bit, the error went off. Thanks for sharing! – narko Sep 06 '19 at 09:12
2

If you create a fragment with a bundle, but without NavController.navigate, and you still want to keep "by navArgs()" in destination fragment e.g because you want to use it in launchFragmentInContainer when testing, then use try/catch. The IllegalStateException is thrown before you start checking nullable type.

private fun updateArguments(){
    this.title = try{
        val args: DestinationFragmentArgs by navArgs()

        if (args.dataObject?.name == ""){
            // default value provided so check whether bundle was used
            arguments?.getString("name") ?: ""
        }else {
            args?.venueId
        }
    }catch (ex: Exception){
        // fragment created without using NavController.navigate
        arguments?.getString("venueId") ?: ""
    }
}

Room is very fast, so retrieving an object from the id as an argument should not be an issue, so try using primitive types for your arguments if possible, otherwise use Parcelable or Serialize for inexpensive objects.

Pitos
  • 659
  • 6
  • 23