Используя Компонент Архитектуры Навигации, мы также имеем динамическую навигацию от кода, подобного этому в некоторых случаях:

navController.navigate(R.id.cartFragment)

Затем, когда нам нужно перейти к новому корневому назначению (обратная навигация закроет приложение) из этого назначения, у нас нет возможности узнать, каким является наше текущее корневое назначение, поэтому мы не знаем, как установить назначение popUpTo.

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

Если мы установим его в пункт назначения, которого нет в backstack, то мы получим журнал предупреждений (из popBackStackInternal в NavController):

«Игнорирование popBackStack по назначению *: id / splashFragment, поскольку он не был найден в текущем заднем стеке»

Случай случается, потому что у нас также есть несколько случаев до этого в потоке, где мы устанавливаем PopUpTo так, чтобы в зависимости от потока у нас были разные корневые пункты назначения.

Я посмотрел на все доступные методы в NavController и попытался поразмышлять над mBackStack, но не смог найти способ очистить backstack.

Как очистить backstack, не зная текущего корневого пункта назначения?


Изменить: добавлен пример навигационного графика

<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, мы будем динамически переходить либо к Onboarding, Login или Cart в зависимости от состояния. Если это, мы переходим к логину и после успешного входа динамически переходим к корзине.

В обоих случаях общей динамической навигации мы не знаем, каково наше корневое назначение, и хотя не можем правильно установить popUpTo. При вызове из Splash это SplashFragment, при вызове из Login это LoginFragment или другой случай при оформлении заказа это может быть CartFragment или другой фрагмент, который является корневым назначением.

Как определить динамически, что является корневым назначением, или просто очистить backstack как часть навигации?

Хотя это немного упрощено, так как в нашем приложении есть больше случаев того же шаблона.

Изменить 2. Определение корневого пункта назначения Я определяю корневой пункт назначения как пункт назначения для popUpTo с включительно, чтобы очистить полный backstack, так что обратное действие закроет приложение. Например, Splash -> Login очистит заставку с помощью popUpTo, затем Login to Cart должен очистить оставшуюся часть backstack, но теперь корневым пунктом назначения является не Splash, а Login as Splash при переходе к Login.

0
Morten Holmgaard 25 Сен 2019 в 14:42

2 ответа

Лучший ответ

В итоге мы решили это двумя способами.

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`)
        }
    }
}
0
Morten Holmgaard 13 Ноя 2019 в 08:27