导航组件 Kotlin - 无法从当前目的地找到

Cyd*_*Cyd 21 android kotlin android-architecture-navigation

我有一个片段 A、B、C。从 A -> B 导航时没问题,但从 B -> C 时它崩溃了。

这是我的导航

在此处输入图片说明

这是我的导航代码

 categoryProductItemListAdapter.setOnItemClickListener {
        val action = CategoryProductItemsDirections.actionCategoryProductItems2ToProductItem(null, it)
        navController = Navigation.findNavController(requireView())
        navController?.navigateUp()
        navController?.navigate(action)
    }
Run Code Online (Sandbox Code Playgroud)

这是 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>
Run Code Online (Sandbox Code Playgroud)

这是错误:

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
Run Code Online (Sandbox Code Playgroud)

我不知道发生了什么,但似乎 navController 正在寻找“navigation_home”

vis*_*nny 17

我创建了一个扩展函数来检查从当前目的地开始操作的可行性。

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) }
    }
}}
Run Code Online (Sandbox Code Playgroud)

orEmpty()部分扩展Int?如下:

fun Int?.orEmpty(default: Int = 0): Int {
    return this ?: default
}
Run Code Online (Sandbox Code Playgroud)


Del*_*ark 17

这与其说是一个答案,不如说是一个提醒。但我希望它有所帮助。

总结:(正如其他人已经说过的:)对 Navigation 函数的连续调用是大多数这些异常的原因。

考虑到 android 组件的结构,特别是 MediatorLiveData 的工作方式,人们有时可能希望将数据节点加入单个可观察数据持有者 (LiveData)。

如果将此中介体的观察与动态导航功能联系起来,则无疑会出现错误。

原因是源可能会连续更改 LiveData 值,次数等于连接到 Mediator 的源数量。

这是一个非常好的主意,但是。对 NavController 的重复更改肯定会导致不良结果。

这可能包括:

  • 两次弹出 backStack。

  • 连续两次从 A -> B
    出现第二次未找到 A 的异常。

这是一个很大的测试问题,特别是因为一个 Fragment 的问题可能会级联到底层堆栈,因此当一个 Fragment 中可能出现 Direction not found 的异常时,真正的罪魁祸首可能是在给定的那个的 TOP 上的 Fragment 上例外。

实际上,这可以通过创建一个scheduled.cancel(true);对 mediatorLiveData 本身具有延迟容忍度的自取消线程执行器来轻松解决 (准确地说是 onChange,而不是 setValue(),因为急切的内部状态更新是中介的全部且唯一的目的/笑话恕我直言(抱歉,不允许使用 postValue()!))。

更不用说中介本身是一个不完整的组件......

另一种更简单的方法是确保当且仅当 !Object::Equals 时才执行来自 MutableLiveData 的 onChange 调用,并防止重复调用 onChange(),这仍然是 MediatorLiveData/LiveData 不完整的证明。(对列表要特别小心)

不惜一切代价避免对 NavController 执行连续调用,如果您必须以某种方式执行,那么延迟运行可能是您实现它的唯一方法。


Ind*_*ana 7

首先,requireView()在尝试检索您的导航控制器时不应该通过- navController = Navigation.findNavController(requireView())。您应该传递实际的导航主机片段实例。

其次,问题是因为您试图在片段 A 上从 B -> C 调用导航路径。

您的方向路径是从 B -> C

val action = CategoryProductItemsDirections.actionCategoryProductItems2ToProductItem(null, it)
Run Code Online (Sandbox Code Playgroud)

但是您首先向上导航,因此在尝试执行导航时您实际上现在位于片段 A 上:

navController?.navigateUp()
navController?.navigate(action)
Run Code Online (Sandbox Code Playgroud)

  • 在处理像 ViewPager 这样的嵌套片段时,您很容易犯尝试从嵌套页面导航到另一个片段的错误,而实际上导航必须是从 ViewPager 片段到目标片段。 (5认同)
  • 您的回答帮助我解决了我在评论中提到的问题。也就是说,尝试从您当前不在的目的地进行导航。 (2认同)

小智 7

//通过传递id检查当前fragment是否是事件触发fragment

 fun Fragment.findNavControllerSafely(id: Int): NavController? {
        return if (findNavController().currentDestination?.id == id) {
            findNavController()
        } else {
            null
        }
    }
Run Code Online (Sandbox Code Playgroud)

//在调用导航的片段中实现

findNavControllerSafely(R.id.fragment1)?.navigate(
                    R.id.action_fragment1_to_fragment2, bundle
                )
Run Code Online (Sandbox Code Playgroud)


Nob*_*dy8 6

下面是用于安全导航的 Java 版本的 NavigationUtils 类:

public abstract class NavigationUtils {

    /**
     * This function will check navigation safety before starting navigation using direction
     *
     * @param navController NavController instance
     * @param direction     navigation operation
     */
    public static void navigateSafe(NavController navController, NavDirections direction) {
        NavDestination currentDestination = navController.getCurrentDestination();

        if (currentDestination != null) {
            NavAction navAction = currentDestination.getAction(direction.getActionId());

            if (navAction != null) {
                int destinationId = orEmpty(navAction.getDestinationId());

                NavGraph currentNode;
                if (currentDestination instanceof NavGraph)
                    currentNode = (NavGraph) currentDestination;
                else
                    currentNode = currentDestination.getParent();

                if (destinationId != 0 && currentNode != null && currentNode.findNode(destinationId) != null) {
                    navController.navigate(direction);
                }
            }
        }
    }


    /**
     * This function will check navigation safety before starting navigation using resId and args bundle
     *
     * @param navController NavController instance
     * @param resId         destination resource id
     * @param args          bundle args
     */
    public static void navigateSafe(NavController navController, @IdRes int resId, Bundle args) {
        NavDestination currentDestination = navController.getCurrentDestination();

        if (currentDestination != null) {
            NavAction navAction = currentDestination.getAction(resId);

            if (navAction != null) {
                int destinationId = orEmpty(navAction.getDestinationId());

                NavGraph currentNode;
                if (currentDestination instanceof NavGraph)
                    currentNode = (NavGraph) currentDestination;
                else
                    currentNode = currentDestination.getParent();

                if (destinationId != 0 && currentNode != null && currentNode.findNode(destinationId) != null) {
                    navController.navigate(resId, args);
                }
            }
        }
    }

    private static int orEmpty(Integer value) {
        return value == null ? 0 : value;
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以像这样使用这个类:

NavController navController = Navigation.findNavController(view);
NavigationUtils.navigateSafe(navController, R.id.action_firstFragment_to_secondFragment, null);
Run Code Online (Sandbox Code Playgroud)

或者:

NavController navController = Navigation.findNavController(view);
NavDirections direction = FirstFragmentDirections.actionFirstFragmentToSecondFragment(yourModel, bundleArgs);
NavigationUtils.navigateSafe(navController, direction);
Run Code Online (Sandbox Code Playgroud)

  • 这对我来说很有用,对我的 kotlin 代码库进行了一些修改,但主要思想是相同的。它实际上对我很有帮助。 (2认同)