使用 PopUpTo 导航,但不知道您的根目的地是什么 - 导航架构组件

Mor*_*ard 6 android android-architecture-navigation

使用导航架构组件,在某些情况下我们还可以通过如下代码进行动态导航:

\n\n
navController.navigate(R.id.cartFragment)\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后,当我们需要从该目的地导航到新的根目的地(后退导航将关闭应用程序)时,我们无法知道当前的根目的地是什么,因此我们不知道将 popUpTo 目的地设置为什么。

\n\n
val navigationOptions = NavOptions.Builder().setPopUpTo(R.id.???, true).build()\nnavController.navigate(R.id.loginFragment, null, navigationOptions)\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果我们将其设置为不在后台堆栈中的目的地,那么我们会收到警告日志(来自popBackStackInternalin NavController):

\n\n
\n

“忽略 popBackStack 到目标 *:id/splashFragment,因为在当前返回堆栈上找不到它”

\n
\n\n

发生这种情况是因为我们之前在流程中也有多种情况,我们将 PopUpTo 设置为依赖于流程,因此我们有不同的根目的地。

\n\n

我已经查看了所有可用的方法NavController并尝试了一些反思mBackStack,但我无法找到清除后台堆栈的方法。

\n\n

在不知道当前根目标时如何清除后退堆栈?\n


\n编辑:添加导航图示例\n

\n\n
<fragment\n    android:id="@+id/navigation_cart"\n    android:name="ProjectsFragment_"\n    android:label="Cart"\n    tools:layout="@layout/fragment_cart" />\n\n<fragment\n    android:id="@+id/loginFragment"\n    android:name="LoginFragment_"\n    android:label="Login"\n    tools:layout="@layout/fragment_login">\n    <--! Dynamic navigation to either Consent or Cart based on state -->\n    <action\n        android:id="@+id/action_login_to_home"\n        app:destination="@id/navigation_cart"\n        app:popUpTo="@id/loginFragment"\n        app:popUpToInclusive="true" />\n    <action\n        android:id="@+id/action_loginFragment_to_consentFragment"\n        app:destination="@id/consentFragment" />\n    <action\n        android:id="@+id/action_loginFragment_to_contactFragment"\n        app:destination="@id/contactFragment" />\n</fragment>\n\n<fragment\n    android:id="@+id/splashFragment"\n    android:name="SplashFragment_"\n    android:label="SplashFragment"\n    tools:layout="@layout/fragment_splash">\n    <--! Dynamic navigation to either Onboarding, Login or Cart based on state -->\n</fragment>\n<dialog\n    android:id="@+id/consentFragment"\n    android:name="ConsentFragment"\n    android:label="ConsentFragment"\n    tools:layout="@layout/fragment_consent" />\n<fragment\n    android:id="@+id/onboardingIntroFragment"\n    android:name="OnboardingIntroFragment_"\n    android:label="OnboardingIntroFragment"\n    tools:layout="@layout/fragment_onboarding">\n    <action\n        android:id="@+id/action_onboardingIntroFragment_to_loginFragment"\n        app:destination="@id/loginFragment"\n        app:popUpTo="@id/onboardingIntroFragment"\n        app:popUpToInclusive="true" />\n</fragment>\n<dialog\n    android:id="@+id/contactFragment"\n    android:name="ContactFragment"\n    android:label="ContactFragment"\n    tools:layout="@layout/fragment_contact" />\n
Run Code Online (Sandbox Code Playgroud)\n\n

\n\n

关于 SplashFragment 中的上述具体情况,我们将根据状态动态导航到 Onboarding、Login 或 Cart。如果我们导航到登录,成功登录后动态导航到购物车。

\n\n

在一般动态导航的两种情况下,我们不知道我们的根目的地是什么,但无法正确设置 popUpTo。当从 Splash 调用时,它是 SplashFragment;当从 Login 调用时,它是 LoginFragment;或者在结账时,它可能是 CartFragment 或另一个作为根目标的片段。

\n\n

如何动态地找出根目的地是什么,或者只是清除后退堆栈作为导航的一部分?

\n\n

尽管这有点简化,因为我们的应用程序中有更多相同模式的情况。

\n\n

编辑 2:根目标定义\n我将根目标定义为 popUpTo 的目标,并包含清除完整的后台堆栈,s\xc3\xa5 反向操作将关闭应用程序。\n例如 Splash -> Login 将使用 popUpTo 清除启动,然后登录到购物车应该会清除后台堆栈的其余部分,但现在根目标不是 Splash,而是登录,因为在登录时会弹出 Splash。

\n

Mor*_*ard 1

我们最终通过两种方式解决了这个问题。

1)当我们可以从动态导航位置找出当前根目的地是什么时,我们将该参数解析到动态导航器中。

object Navigator {
    fun showStartFragment(navController: NavController, @IdRes currentDestination: Int) {
        val navigationOptions = NavOptions.Builder().setPopUpTo(currentDestination, true).build()

        if (!Settings.Intro.onboardingCompleted) {
            navController.navigate(R.id.onboardingIntroFragment, null, navigationOptions)
        } else {
            val isLoggedIn = Settings.User.loggedIn
            if (isLoggedIn) {
                MainActivity.shared?.mainStateHandler?.showNextFragment(navController, currentDestination)
            } else {
                navController.navigate(R.id.loginFragment, null, navigationOptions)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


2)但我们也遇到了异步端点响应可能导致用户必须注销、对过期令牌执行操作或用户已被阻止的情况。为了解决这个问题,我们破解了 NavController 以通过反射获取根目的地。

fun NavController.getRootDestination(): NavDestination? {
    val mBackStack = NavController::class.java.getDeclaredField("mBackStack").let {
        it.isAccessible = true
        return@let it.get(this) as Deque<*>
    }
    for (entry in mBackStack) {
        val destination = Reflector.getMethodResult(entry, "getDestination") as NavDestination
        if (destination !is NavGraph) {
            return destination
        }
    }
    return null
}

object Reflector {
    fun getMethodResult(`object`: Any, methodName: String): Any? {
        return `object`.javaClass.getMethod(methodName).let {
            it.isAccessible = true
            return@let it.invoke(`object`)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)