6

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

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

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

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

4

2 回答 2

7

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

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

示例导航图

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

因此,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

可以在此处找到示例回购


我的最佳猜测

这个问题是由于旧导航版本中的错误,所以使用2.2.0-alpha01我在示例存储库中使用的。

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

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

这修复了错误

不能内联字节码...


请记住,不建议将复杂对象作为参数传递。引用自docs

一般来说,您应该强烈希望在目的地之间只传递最少量的数据。例如,您应该传递一个键来检索对象,而不是传递对象本身,因为所有已保存状态的总空间在 Android 上是有限的。如果您需要传递大量数据,请考虑使用在片段之间共享数据中所述的 ViewModel。

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

于 2019-08-22T11:07:22.283 回答
2

如果您使用捆绑包创建片段,但没有 NavController.navigate,并且您仍然希望在目标片段中保留“通过 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。

于 2020-09-17T00:26:16.483 回答