【问题标题】:Navigation Component - Starting destination is incorrect导航组件 - 起始目的地不正确
【发布时间】:2021-08-03 14:45:24
【问题描述】:

前几天我开始注意到我的应用中有一些东西,而且非常不一致。有时会发生,有时不会。

我正在使用导航组件来处理应用程序中的导航,我开始注意到有时,当通过操作栏后退按钮或设备后退按钮弹出后退堆栈时,它会返回到不再是起始目的地的片段(或者至少不应该)。

在我的例子中,应用程序从 MainFragment 开始,一旦经过身份验证,就会移动到 DashboardFragment。这是很常见的情况。

应用程序中的导航非常平坦。大多数情况下,它只有 1 级深度,因此几乎所有视图都可以从仪表板访问。

应用程序从登录视图开始,然后到仪表板,会话将保留为“起始目的地”。为此,它使用 popUpTo 和 popUpToInclusive 在 nav_graph 中完成。

<fragment
    android:id="@+id/mainFragment"
    android:name="com.example.view.fragments.MainFragment"
    android:label="Welcome">
    <action
        android:id="@+id/action_mainFragment_to_dashboardFragment"
        app:destination="@id/dashboardFragment"
        app:popUpTo="@id/mainFragment"
        app:popUpToInclusive="true"/>
</fragment>

<fragment
    android:id="@+id/dashboardFragment"
    android:name="com.example.view.fragments.dashboard.DashboardFragment"
    android:label="@string/dashboard_header" >
    <action
        android:id="@+id/action_dashboardFragment_to_notificationsFragment"
        app:destination="@id/notificationsFragment" />
</fragment>

当用户成功验证并进入仪表板时,我使用 NavController.navigate() 将它们发送到那里。

findNavController().navigate(
    MainFragmentDirections.actionMainFragmentToDashboardFragment()
)

// This should have the same result and it does appear to be affected by the same issue
// findNavController().navigate(R.id.action_mainFragment_to_dashboardFragment)

我有一个带有后退箭头和导航抽屉的操作栏。在主要活动中,我需要定义 AppBarConfiguration 并覆盖 onSupportNavigateUp()

lateinit var appBarConfiguration: AppBarConfiguration
...
override fun onCreate(savedInstanceState: Bundle?) {
    Timber.d("onCreate()")
    super.onCreate(savedInstanceState)

    _binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)

    // There is 2 different drawer menu's respectfully.
    appBarConfiguration = AppBarConfiguration(
        setOf(
            R.id.mainFragment,
            R.id.dashboardFragment
        ), binding.drawerLayout
    )

    setSupportActionBar(binding.toolbar)
    setupActionBarWithNavController(navController, appBarConfiguration)
}
...
override fun onSupportNavigateUp(): Boolean {
    Timber.d("-> onSupportNavigateUp()")
    val breadcrumb = navController
        .backStack
        .map { it.destination }
        .filterNot { it is NavGraph }
        .joinToString(" > ") { it.displayName.split('/')[1] }

    Timber.d("Backstack: $breadcrumb")
    Timber.d("Previous backstack entry: ${navController.previousBackStackEntry?.destination?.displayName}")

    return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
...

当我们退后一步时,日志看起来像这样,它工作正常

D/MainActivity: -> onSupportNavigateUp()
D/MainActivity: Backstack: dashboardFragment > testingFragment
D/MainActivity: Previous backstack entry: com.example:id/dashboardFragment
D/DashboardFragment: -> onCreateView()
D/BaseFragment: -> onCreateView()
D/DashboardFragment: -> onViewCreated()

我还注意到在操作栏中使用汉堡包时,它也会调用 onSupportNavigateUp()

D/MainActivity: -> onSupportNavigateUp()
D/MainActivity: pendingAction: false
D/MainActivity: Backstack: dashboardFragment
D/MainActivity: Previous backstack entry: null

当我使用抽屉导航到目的地时,我确实在日志中看到了这一点,但我不确定返回的位置/原因,或者它是否有任何重要性

I/NavController: Ignoring popBackStack to destination com.example:id/mainFragment as it was not found on the current back stack

现在,当它的 正常工作时,这就是日志的样子

D/MainActivity: -> onSupportNavigateUp()
D/MainActivity: Backstack: mainFragment > testingFragment
D/MainActivity: Previous backstack entry: com.example:id/mainFragment
D/MainFragment: -> onCreateView()
D/BaseFragment: -> onCreateView()
D/MainFragment: -> onViewCreated()

当执行从主片段到仪表板片段的导航时,这真的感觉就像 popUpTo 和 popUpToInclusive 属性没有被应用(有时)。同样令人怀疑的是,即使仪表板片段未设置为新的起始目的地,但它也从后台堆栈中丢失。假设未应用属性,我希望看到面包屑Backstack: mainFragment &gt; dashboardFragment &gt; testingFragment

任何帮助将不胜感激!

【问题讨论】:

  • 根据Principles of Navigation,您的欢迎片段绝对不应该是图表的起始目的地。对于这种情况,有一个指南 specifically about conditional navigation
  • @ianhanniballake 应用的欢迎片段是登录片段。没有其他视图(如登陆/欢迎视图)在登录和仪表板之间进行分类。如果用户正确登录,它应该移动到仪表板并设置为新的起点。这样一来,如果应用程序关闭并且在应用程序超时期限内,我们将跳过登录并跳回仪表板。将另一个片段添加到此组合中不会显示视图,因此仅用于逻辑,然后从登录或仪表板退回将需要处理退出应用程序。
  • 这不是那些文档所说的。他们说您的仪表板目标应该是起始目标,并且它应该负责有条件地在用户未登录时将其导航到登录屏幕。
  • @ianhanniballake 根据您的陈述并将我的应用程序结构应用于条件导航示例,您是说我的起始目的地将是个人资料片段。未经身份验证,无法使用此应用程序。登录视图是唯一可以在未经身份验证的情况下看到的视图。假设我将仪表板设置为起始目的地并有条件地重定向到登录,然后我需要将其设置为起始目的地以将用户隔离到该视图。如果他们向上/向后导航,则会返回到必须对其进行身份验证的仪表板。
  • 您的仪表板将是起始目的地 - 导航原则特别指出的固定起始目的地。

标签: android kotlin android-fragments android-architecture-navigation


【解决方案1】:

虽然肯定有更好的程序可供遵循(如先前 cmets 中的导航原则中所述),但更改的开销会引入过多的新错误,并且此时范围太大。

仍然不知道为什么 popUpTo 和 popUpToInclusive 在导航时通过 XML 不可靠(即使使用 NavDirections),但是到目前为止,在导航时传递 NavOptions 似乎可以解决问题。

findNavController().navigate(
    MainFragmentDirections.actionMainFragmentToDashboardFragment(),
    NavOptions.Builder().setPopUpTo(R.id.mainFragment, true).build()
)

到目前为止,问题还没有再次出现。

【讨论】:

    【解决方案2】:

    正如 Ian 的第一个链接中所说:

    后台堆栈始终在堆栈底部具有应用程序的起始目的地。

    您的起始目的地是导航图中的“家”,用小房子图标标识。使用 XML 中的 startDestination 属性设置。当 Navigation 库创建一个 backstack 时,它总是在底部有那个目的地。即使您尝试使用poUpTo 属性来避免它,它也会一直存在。

    这就是为什么如果有一个片段你认为是你的“家”片段,比如你的仪表板,它需要明确地是你的startDestination。如果用户退出您的应用程序,这是他们看到的最后一件事。如果您将登录或欢迎屏幕之类的内容作为起始目的地,他们会退回到那里。

    这就是为什么您需要将“家”片段设置为起始目的地,然后设置handle any extra navigation to a login or welcome screen from there。这就是导航东西的设计方式。如果您尝试解决它(我做到了!),您会遇到其他问题,并且许多不错的功能(例如自动后台重新创建)可能无法正常工作

    【讨论】:

      猜你喜欢
      • 2021-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多