使用PopUpTo导航,但不知道您的根目录是什么-导航体系结构组件

时间:2019-09-25 11:42:25

标签: android android-architecture-navigation

在某些情况下,使用导航体系结构组件,我们还可以从这样的代码进行动态导航:

navController.navigate(R.id.cartFragment)

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

val navigationOptions = NavOptions.Builder().setPopUpTo(R.id.???, true).build()
navController.navigate(R.id.loginFragment, null, navigationOptions)

如果将其设置为不位于后退栈中的目标,则将获得警告日志(从popBackStackInternal中的NavController):

  

“正在将popBackStack忽略到目标*:id / splashFragment,因为它在当前后退堆栈中找不到”

之所以会发生这种情况,是因为流中还有很多情况,我们将PopUpTo设置为取决于流,因此我们有不同的根目标。

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

在不知道当前根目录目的地的情况下如何清除后堆栈?


编辑:添加了导航图示例

<fragment
    android:id="@+id/navigation_cart"
    android:name="ProjectsFragment_"
    android:label="Cart"
    tools:layout="@layout/fragment_cart" />

<fragment
    android:id="@+id/loginFragment"
    android:name="LoginFragment_"
    android:label="Login"
    tools:layout="@layout/fragment_login">
    <--! Dynamic navigation to either Consent or Cart based on state -->
    <action
        android:id="@+id/action_login_to_home"
        app:destination="@id/navigation_cart"
        app:popUpTo="@id/loginFragment"
        app:popUpToInclusive="true" />
    <action
        android:id="@+id/action_loginFragment_to_consentFragment"
        app:destination="@id/consentFragment" />
    <action
        android:id="@+id/action_loginFragment_to_contactFragment"
        app:destination="@id/contactFragment" />
</fragment>

<fragment
    android:id="@+id/splashFragment"
    android:name="SplashFragment_"
    android:label="SplashFragment"
    tools:layout="@layout/fragment_splash">
    <--! Dynamic navigation to either Onboarding, Login or Cart based on state -->
</fragment>
<dialog
    android:id="@+id/consentFragment"
    android:name="ConsentFragment"
    android:label="ConsentFragment"
    tools:layout="@layout/fragment_consent" />
<fragment
    android:id="@+id/onboardingIntroFragment"
    android:name="OnboardingIntroFragment_"
    android:label="OnboardingIntroFragment"
    tools:layout="@layout/fragment_onboarding">
    <action
        android:id="@+id/action_onboardingIntroFragment_to_loginFragment"
        app:destination="@id/loginFragment"
        app:popUpTo="@id/onboardingIntroFragment"
        app:popUpToInclusive="true" />
</fragment>
<dialog
    android:id="@+id/contactFragment"
    android:name="ContactFragment"
    android:label="ContactFragment"
    tools:layout="@layout/fragment_contact" />

关于上述SplashFragment中的具体情况,我们将根据状态动态导航到“入职”,“登录”或“购物车”。如果是这样,我们导航到“登录”,成功登录后,动态导航到“购物车”。

我在一般动态导航的两种情况下都不知道我们的根目录是什么,尽管不能正确设置popUpTo。从Splash调用时为SplashFragment,从Login调用时为LoginFragment,或者在结帐时出现的另一种情况可能是CartFragment或作为根目标的另一个片段。

如何动态地确定根目标是什么,或者只是在导航中清除后退堆栈?

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

编辑2:根目标定义 我将根目标定义为popUpTo的目标(包括端点),以清除整个后退堆栈,否则后退操作将关闭应用程序。 例如,Splash-> Login将使用popUpTo清除飞溅,然后Login to Cart应清除其余的堆栈,但现在根目录不是Splash,而是Login,因为登录时弹出Splash。

2 个答案:

答案 0 :(得分:0)

答案 1 :(得分:0)

我们最终有两种解决方法。

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)
            }
        }
    }
}


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`)
        }
    }
}