Jetpack 导航组件在加载具有可为空参数的起始目的地时抛出 IllegalStateException

Jetpack navigation component throws an IllegalStateException when loading a start destination with nullable argument

我向起始目的地添加了一个可为空的参数:

<?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>

但是当我加载我的应用程序时,出现以下异常:

java.lang.IllegalStateException: Fragment MyStartFragment{a4ffd1f (ca52d4dc-ff36-4a93-8ebf-f11af7b7d5aa) id=0x7f080145} has null arguments
    at com.myapp.MyStartFragment$$special$$inlined$navArgs.invoke(FragmentNavArgsLazy.kt:42)
    at com.myapp.MyStartFragment$$special$$inlined$navArgs.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)

而异常是由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
    ...
}

这里是 MyDataObject 的代码:

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

我不明白的是我的起始目的地没有通过导航控制器正确地传递参数。我在这里遗漏了什么吗?

您好,我假设您想实现如下目标

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

此流程与首次使用、返回的用户流程类似。这里 BeforeFragmentlogin_nav_graph 嵌套图中的最后一个片段。 StartFragmentmain_nav_graph 的起始目的地。 StartFragment 是返回用户看到的第一个屏幕。

所以在BeforeFragment中你可以如下设置args

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

并且在 StartFragment 中,您可以像之前那样进行以下操作

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

可以找到示例回购 here


我最好的猜测

此问题是由于旧导航版本中的错误,因此请使用我在示例存储库中使用的 2.2.0-alpha01

为了修复移动到新导航版本时出现的错误 在您的模块 gradle 文件中添加以下内容

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

这修复了错误

Cannot inline byte code ...


请记住,不建议将复杂对象作为参数传递。 引用自 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.

如果这解决了您的问题,请确认答案,因为我花了很多时间准备这个 post。

如果您创建了一个带有包但没有 NavController.navigate 的片段,并且您仍然希望在目标片段中保留“by navArgs()”,例如因为您希望在测试时在 launchFragmentInContainer 中使用它,那么使用 try/catch。在开始检查可空类型之前抛出 IllegalStateException。

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 非常快,因此从 id 中检索对象作为参数应该不是问题,因此如果可能,请尝试使用基本类型作为参数,否则使用 Parcelable 或 Serialize 来获取便宜的对象。