Android导航组件: Hitting back results in NavController being out of sync

标签 android android-fragments android-jetpack-navigation

我正在通过 FragmentManager 从手动后台操作迁移应用程序使用新的导航库。该应用程序包含一个 MainActivity有一个 DrawerLayout抽屉里有 10-12 件元素。当点击时,会发生一些 fragment 操作并显示一个新 fragment 。
这个生产应用程序中有足够的代码,简单地撕掉旧的导航结构并转移到导航库中是不可行的。
相反,我正在做的是逐个抽屉项目迁移抽屉项目。例如 - 有一个称为支票存款的功能,可以通过抽屉导航中的项目访问。当您点击该项目时,您会看到一个新 fragment ,您可以通过类似于支票存款向导的流程进行操作。到目前为止,我所做的是创建了一个新的 RootCheckDepositView 的 fragment 由单个 FragmentContainerView 组成包含导航图。然后在导航图中导航该支票存款向导的整个“流程”,该导航图最终包含在 RootcheckDeposit 中。 .
这是 RootCheckDeposit 的布局分段:

<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:name="androidx.navigation.fragment.NavHostFragment"
    app:defaultNavHost="true"
    app:navGraph="@navigation/check_deposit_graph" />
虽然与工具栏的集成仍然有点不稳定,但大多数情况下都可以。
但我遇到的主要问题是,当您通过此向导进行时,回击可能会导致 NavController进入一种似乎认为它位于与实际不同的 fragment 上的状态。这在支票存款向导中的以下流程中可靠地发生:
加载屏幕 -> 条款屏幕 -> 检查存款登陆屏幕。
一旦你进入支票存款登陆屏幕,如果你回击什么都不会发生——这很好。不好的是,如果您随后单击该登录屏幕中的一个按钮,该按钮将引导您完成向导,应用程序将崩溃并显示 IllegalArgumentException。 ,声称我们正在尝试使用 Loading Screen 中不可用的操作.这很奇怪,因为我们不在 Loading Screen 上,我们在 Check Deposit Landing Screen .
这是我的导航图文件供引用:
<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/check_deposit_graph"
    app:startDestination="@id/loadingFragment">

    <fragment
        android:id="@+id/loadingFragment"
        android:name="module.checkdeposit.loading.TermsLoadingFragment"
        android:label="@string/check_deposit_title"
        tools:layout="@layout/terms_loading_fragment" >
        <action
            android:id="@+id/action_loadingFragment_to_termsAndConditionsFragment"
            app:destination="@id/termsAndConditionsFragment"
            app:popUpTo="@id/loadingFragment"
            app:popUpToInclusive="true" />
        <action
            android:id="@+id/action_loadingFragment_to_depositOptionsFragment"
            app:destination="@id/depositLandingFragment"
            app:popUpTo="@id/loadingFragment"
            app:popUpToInclusive="true" />
    </fragment>

    <fragment
        android:id="@+id/termsAndConditionsFragment"
        android:name="module.checkdeposit.terms.TermsAndConditionsFragment"
        android:label="@string/check_deposit_terms_and_conditions"
        tools:layout="@layout/terms_and_conditions_fragment" >
        <action
            android:id="@+id/action_termsAndConditionsFragment_to_depositLandingFragment"
            app:destination="@id/depositLandingFragment"
            app:popUpTo="@id/termsAndConditionsFragment"
            app:popUpToInclusive="true" />
    </fragment>

    <fragment
        android:id="@+id/depositLandingFragment"
        android:name="module.checkdeposit.depositlanding.DepositLandingFragment"
        android:label="@string/check_deposit_title"
        tools:layout="@layout/deposit_landing_fragment" >
        <action
            android:id="@+id/action_depositLandingFragment_to_depositHistoryFragment"
            app:destination="@id/depositHistoryFragment" />
        <action
            android:id="@+id/action_depositLandingFragment_to_scanCheckFragment"
            app:destination="@id/scanCheckFragment" />
    </fragment>

    <fragment
        android:id="@+id/depositHistoryFragment"
        android:name="module.checkdeposit.deposithistory.DepositHistoryFragment"
        android:label="@string/check_deposit_deposit_history"
        tools:layout="@layout/deposit_history_fragment" />

    <fragment
        android:id="@+id/scanCheckFragment"
        android:name="module.checkdeposit.scancheck.ScanCheckFragment"
        android:label=""
        tools:layout="@layout/scan_check_fragment" />
</navigation>
值得注意的是 - 当我们从 Loading screen 出发时至Terms screenLanding Screen我们弹出每个项目是因为我不希望用户能够“返回”到加载屏幕或条款屏幕。
我尝试过的事情:
  • 更改 popUpTo导航图中的逻辑指向实际导航图而不是目的地
  • 检查 navController.popBackStack 的结果是否是 truefalse并采取相应措施

  • 我还梳理了实际的NavController代码,似乎正在发生的是 NavController单击返回时会弹出其内部后台堆栈,但不会更改目的地,因为实际上没有其他目的地可去。看起来就像 NavController然后处于奇怪的不一致状态,它与当前显示的 Fragment不正常/没有指向同一件事。
    有没有其他人成功地完成了这种迁移?
    编辑:我创建了一个 repo,它有一个最小的项目来重新创建我遇到的崩溃:https://github.com/alexsullivan114/ExampleNavigationCrashRepo .这种 repo 模仿了我正在开发的生产应用程序的结构。如果单击幻灯片抽屉项目,则继续单击屏幕中的文本,直到到达 Next Example Fragment。然后回击几次并再次单击文本,您将看到崩溃。
    我确定问题在于我如何设置导航库,我只是不确定正确的方法是无需重写其余的应用程序导航

    最佳答案

    根据 considerations for child and sibling fragments guide :

    Only one FragmentManager is allowed to control the fragment back stack at any given time. If your app shows multiple sibling fragments on the screen at the same time, or if your app uses child fragments, then one FragmentManager must be designated to handle your app's primary navigation.

    To define the primary navigation fragment inside of a fragment transaction, call the setPrimaryNavigationFragment() method on the transaction, passing in the instance of the fragment whose childFragmentManager should have primary control.


    Interact programmatically with Navigation 中所述, app:defaultNavHost="true"正在调用setPrimaryNavigationFragment()NavHostFragment 上为您服务本身,但你 不是 调用setPrimaryNavigationFragment()在父 fragment 上 - 你的 RootSlideshowFragment .这意味着 自动正确处理后退按钮的逻辑被调用了——你基本上打破了从 Activity 到父 fragment 到 NavHostFragment 的链。 .即使没有后退堆栈,这也会导致您拦截后退按钮(当您弹出最后一个 fragment 时,导航实际上不会删除它,否则当您的 Activity 的退出动画运行时它不会出现)。
    因此,要解决此问题,您需要:
  • 删除您的 OnBackPressedCallback来自 RootSlideshowFragment .它不是必需的。
  • 当您在 MainActivity 中的 fragment 之间交换时, 调用setPrimaryNavigationFragment()在新 fragment 上:
  • navView.setNavigationItemSelectedListener { menuitem ->
        val newFragment = when (menuitem.itemId) {
            R.id.nav_home -> HomeFragment()
            R.id.nav_gallery-> GalleryFragment()
            else -> RootSlideshowFragment()
        }
        supportFragmentManager.beginTransaction()
            .replace(R.id.container, newFragment)
            .setPrimaryNavigationFragment(newFragment)
            .commit()
        true
    }
    

    关于Android导航组件: Hitting back results in NavController being out of sync,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65240348/

    相关文章:

    android - 如何检查 putExtra 是否存在?

    Android 自定义 View 大小

    安卓游戏UDP/TCP?

    java - 如何在 Android 中使用单选按钮组 ID 在不同 fragment 之间进行切换

    android - 如何在 Jetpack Compose 中强制某些屏幕的方向?

    android - 根据网络响应更新 Activity 的正确方法是什么?

    Android:ShowCase View 只显示一次并重复动画

    java - 恢复 android 的搜索 View 小部件的状态

    android - 应用栏上显示多个选项菜单

    android - 如何将 `rememberNavController` 从喷气背包组合成使用刀柄的 Activity ?