导航组件 Kotlin - 无法从当前目的地找到
Navigation component Kotlin - cannot be found from the current destination
我有片段A、B、C。从 A -> B 导航时没问题,但从 B -> C 导航时它崩溃了。
这是我的导航
这是我的导航代码
categoryProductItemListAdapter.setOnItemClickListener {
val action = CategoryProductItemsDirections.actionCategoryProductItems2ToProductItem(null, it)
navController = Navigation.findNavController(requireView())
navController?.navigateUp()
navController?.navigate(action)
}
这是 productItem
目的地的 XML 代码
<fragment
android:id="@+id/categoryProductItems2"
android:name="com.sample.store.main.dashboard.ui.ui.home.categoryitems.CategoryProductItems"
android:label="CategoryProductItems"
tools:layout="@layout/fragment_category_product_items">
<argument
android:name="category_global"
app:argType="com.sample.store.data.globalmodels.response.categories.Category" />
<action
android:id="@+id/action_categoryProductItems2_to_productItem"
app:destination="@id/productItem"
app:enterAnim="@anim/enter_from_right"
app:exitAnim="@anim/exit_to_right"
app:popEnterAnim="@anim/fragment_open_enter"
app:popExitAnim="@anim/fragment_fade_exit" />
</fragment>
这里是错误:
java.lang.IllegalArgumentException: Navigation action/destination com.sample.store.full:id/action_categoryProductItems2_to_productItem cannot be found from the current destination Destination(id/navigation_home) label=Home class=com.sample.store.main.dashboard.ui.ui.home.mainui.HomeFragment
我不知道发生了什么,但 navController 似乎正在寻找“navigation_home”
首先,您在尝试检索导航控制器时不应通过 requireView()
- navController = Navigation.findNavController(requireView())
。您应该传递实际的 Navigation Host Fragment 实例。
其次,导致问题的原因是您在片段 A 上尝试从 B -> C 调用导航路径。
你的方向路径是从B -> C
val action = CategoryProductItemsDirections.actionCategoryProductItems2ToProductItem(null, it)
但是您首先向上导航,因此当您尝试执行导航时实际上现在位于片段 A 上:
navController?.navigateUp()
navController?.navigate(action)
您需要设置 defaultNavHost="true"。就像这个例子:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/myNavHostFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/navigation_layout" />
</LinearLayout>
另外不要忘记在您的导航组件中设置主页 activity。
对于我的情况,我通过替换 -
来解决问题
<action
android:id="@+id/action_categoryProductItems2_to_productItem"
app:destination="@id/productItem"
app:enterAnim="@anim/enter_from_right"
app:exitAnim="@anim/exit_to_right"
app:popEnterAnim="@anim/fragment_open_enter"
app:popExitAnim="@anim/fragment_fade_exit"/>
和
<action
android:id="@+id/action_categoryProductItems2_to_productItem"
app:destination="@id/productItem"
app:enterAnim="@anim/enter_from_right"
app:exitAnim="@anim/exit_to_right"
app:popEnterAnim="@anim/fragment_open_enter"
app:popExitAnim="@anim/fragment_fade_exit"
app:popUpToInclusive="true" /* If true then also remove the destination from stack while popup */
app:popUpTo="@id/navigation_home"/> /*The fragment where to land again from destination*/
我创建了一个扩展函数来检查从当前目的地开始一个动作的可行性。
fun NavController.navigateSafe(@IdRes resId: Int, args: Bundle? = null) {
val destinationId = currentDestination?.getAction(resId)?.destinationId.orEmpty()
currentDestination?.let { node ->
val currentNode = when (node) {
is NavGraph -> node
else -> node.parent
}
if (destinationId != 0) {
currentNode?.findNode(destinationId)?.let { navigate(resId, args) }
}
}}
orEmpty()
部分是对 Int?
的扩展,如下所示:
fun Int?.orEmpty(default: Int = 0): Int {
return this ?: default
}
这与其说是答案,不如说是提醒。但我希望它能有所帮助。
总结:(正如其他人已经说过的:)连续调用导航函数是大多数异常的原因。
考虑到 android 组件的结构,特别是 MediatorLiveData 的工作方式,人们有时可能希望将数据节点加入到单个可观察数据容器 (LiveData) 中。
如果对这个调解器的观察与动态导航功能相关联,毫无疑问会出现错误。
原因是源可能会连续更改 LiveData 值的次数等于连接到调解器的源的数量。
这是一个非常好的主意,但是。反复修改 NavController 肯定会导致不良结果。
这可能包括:
弹出 backStack 两次。
连续两次从 A -> B 给出异常 A not found
第二次.
这是一个很大的测试问题,因为一个Fragment的问题可能会级联到底层堆栈,所以当一个Fragment可能出现Direction not found的异常时,真正的罪魁祸首可能是在Fragment上给出异常的 TOP。
实际上,这可以通过创建一个自取消线程执行器 scheduled.cancel(true);
来轻松解决,该线程执行器对 mediatorLiveData 本身具有延迟容忍度(准确地说是 onChange,而不是 setValue(),因为急切的内部状态更新是恕我直言,调解员的全部 purpose/joke(抱歉,不允许使用 postValue()!))。
更不用说调解器本身就是一个不完整的组件...
另一种更简单的方法是确保当且仅当 !Object::Equals 时才执行来自 MutableLiveData 的 onChange 调用,并防止重复调用 onChange(),这仍然是 MediatorLiveData/LiveData。 (对列表要格外小心)
不惜一切代价避免对 NavController 执行连续调用,如果您必须这样做,那么延迟运行可能是实现它的唯一方法。
对于我的情况,我通过替换
解决了问题
implementation "android.arch.navigation:navigation-fragment-ktx:1.0.0"
和
implementation "androidx.navigation:navigation-fragment-ktx:2.3.5"
这种错误主要出现在元素列表中,点击某个项目会触发导航。
我用这段代码解决了,在调用导航功能之前点击项目我会检查当前目的地是否是预期的目的地因为,
val currentDestinationIsHome = this.findNavController().currentDestination == this.findNavController().findDestination(R.id.nav_home)
val currentDestinationIsDetail = this.findNavController().currentDestination == this.findNavController().findDestination(R.id.nav_detail)
if(currentDestinationIsHome && !currentDestinationIsDetail){
....
// perform navigation
}
这确保仅当目的地处于合法状态时才进行导航。 [没有 IllegalStateException ... :))]
我有片段A、B、C。从 A -> B 导航时没问题,但从 B -> C 导航时它崩溃了。
这是我的导航
这是我的导航代码
categoryProductItemListAdapter.setOnItemClickListener {
val action = CategoryProductItemsDirections.actionCategoryProductItems2ToProductItem(null, it)
navController = Navigation.findNavController(requireView())
navController?.navigateUp()
navController?.navigate(action)
}
这是 productItem
目的地的 XML 代码<fragment
android:id="@+id/categoryProductItems2"
android:name="com.sample.store.main.dashboard.ui.ui.home.categoryitems.CategoryProductItems"
android:label="CategoryProductItems"
tools:layout="@layout/fragment_category_product_items">
<argument
android:name="category_global"
app:argType="com.sample.store.data.globalmodels.response.categories.Category" />
<action
android:id="@+id/action_categoryProductItems2_to_productItem"
app:destination="@id/productItem"
app:enterAnim="@anim/enter_from_right"
app:exitAnim="@anim/exit_to_right"
app:popEnterAnim="@anim/fragment_open_enter"
app:popExitAnim="@anim/fragment_fade_exit" />
</fragment>
这里是错误:
java.lang.IllegalArgumentException: Navigation action/destination com.sample.store.full:id/action_categoryProductItems2_to_productItem cannot be found from the current destination Destination(id/navigation_home) label=Home class=com.sample.store.main.dashboard.ui.ui.home.mainui.HomeFragment
我不知道发生了什么,但 navController 似乎正在寻找“navigation_home”
首先,您在尝试检索导航控制器时不应通过 requireView()
- navController = Navigation.findNavController(requireView())
。您应该传递实际的 Navigation Host Fragment 实例。
其次,导致问题的原因是您在片段 A 上尝试从 B -> C 调用导航路径。
你的方向路径是从B -> C
val action = CategoryProductItemsDirections.actionCategoryProductItems2ToProductItem(null, it)
但是您首先向上导航,因此当您尝试执行导航时实际上现在位于片段 A 上:
navController?.navigateUp()
navController?.navigate(action)
您需要设置 defaultNavHost="true"。就像这个例子:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/myNavHostFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/navigation_layout" />
</LinearLayout>
另外不要忘记在您的导航组件中设置主页 activity。
对于我的情况,我通过替换 -
来解决问题 <action
android:id="@+id/action_categoryProductItems2_to_productItem"
app:destination="@id/productItem"
app:enterAnim="@anim/enter_from_right"
app:exitAnim="@anim/exit_to_right"
app:popEnterAnim="@anim/fragment_open_enter"
app:popExitAnim="@anim/fragment_fade_exit"/>
和
<action
android:id="@+id/action_categoryProductItems2_to_productItem"
app:destination="@id/productItem"
app:enterAnim="@anim/enter_from_right"
app:exitAnim="@anim/exit_to_right"
app:popEnterAnim="@anim/fragment_open_enter"
app:popExitAnim="@anim/fragment_fade_exit"
app:popUpToInclusive="true" /* If true then also remove the destination from stack while popup */
app:popUpTo="@id/navigation_home"/> /*The fragment where to land again from destination*/
我创建了一个扩展函数来检查从当前目的地开始一个动作的可行性。
fun NavController.navigateSafe(@IdRes resId: Int, args: Bundle? = null) {
val destinationId = currentDestination?.getAction(resId)?.destinationId.orEmpty()
currentDestination?.let { node ->
val currentNode = when (node) {
is NavGraph -> node
else -> node.parent
}
if (destinationId != 0) {
currentNode?.findNode(destinationId)?.let { navigate(resId, args) }
}
}}
orEmpty()
部分是对 Int?
的扩展,如下所示:
fun Int?.orEmpty(default: Int = 0): Int {
return this ?: default
}
这与其说是答案,不如说是提醒。但我希望它能有所帮助。
总结:(正如其他人已经说过的:)连续调用导航函数是大多数异常的原因。
考虑到 android 组件的结构,特别是 MediatorLiveData 的工作方式,人们有时可能希望将数据节点加入到单个可观察数据容器 (LiveData) 中。
如果对这个调解器的观察与动态导航功能相关联,毫无疑问会出现错误。
原因是源可能会连续更改 LiveData 值的次数等于连接到调解器的源的数量。
这是一个非常好的主意,但是。反复修改 NavController 肯定会导致不良结果。
这可能包括:
弹出 backStack 两次。
连续两次从 A -> B 给出异常 A not found
第二次.
这是一个很大的测试问题,因为一个Fragment的问题可能会级联到底层堆栈,所以当一个Fragment可能出现Direction not found的异常时,真正的罪魁祸首可能是在Fragment上给出异常的 TOP。
实际上,这可以通过创建一个自取消线程执行器 scheduled.cancel(true);
来轻松解决,该线程执行器对 mediatorLiveData 本身具有延迟容忍度(准确地说是 onChange,而不是 setValue(),因为急切的内部状态更新是恕我直言,调解员的全部 purpose/joke(抱歉,不允许使用 postValue()!))。
更不用说调解器本身就是一个不完整的组件...
另一种更简单的方法是确保当且仅当 !Object::Equals 时才执行来自 MutableLiveData 的 onChange 调用,并防止重复调用 onChange(),这仍然是 MediatorLiveData/LiveData。 (对列表要格外小心)
不惜一切代价避免对 NavController 执行连续调用,如果您必须这样做,那么延迟运行可能是实现它的唯一方法。
对于我的情况,我通过替换
解决了问题implementation "android.arch.navigation:navigation-fragment-ktx:1.0.0"
和
implementation "androidx.navigation:navigation-fragment-ktx:2.3.5"
这种错误主要出现在元素列表中,点击某个项目会触发导航。
我用这段代码解决了,在调用导航功能之前点击项目我会检查当前目的地是否是预期的目的地因为,
val currentDestinationIsHome = this.findNavController().currentDestination == this.findNavController().findDestination(R.id.nav_home)
val currentDestinationIsDetail = this.findNavController().currentDestination == this.findNavController().findDestination(R.id.nav_detail)
if(currentDestinationIsHome && !currentDestinationIsDetail){
....
// perform navigation
}
这确保仅当目的地处于合法状态时才进行导航。 [没有 IllegalStateException ... :))]