如何在 Android 上使用导航组件打开新片段

How to open new fragment with NavigationComponent on Android

在我的应用程序中,我使用了单个 activity 并使用了一些片段!
我首先要显示 SplashFragment 并检查用户令牌,如果存在则打开 HomeFragment 否则打开 RegisterFragment

我写了下面的代码,但是注册用户后显示下面的错误!

Logcat 错误:

    java.lang.IllegalArgumentException: Navigation action/destination my.app:id/actionSplashToHome cannot be found from the current destination Destination(my.app:id/registerFragment) label=fragment_register class=my.app.ui.register.RegisterFragment
        at androidx.navigation.NavController.navigate(NavController.kt:1536)
        at androidx.navigation.NavController.navigate(NavController.kt:1468)
        at androidx.navigation.NavController.navigate(NavController.kt:1450)
        at androidx.navigation.NavController.navigate(NavController.kt:1433)
        at my.app.ui.splash.SplashFragment$onViewCreated.emit(SplashFragment.kt:43)
        at my.app.ui.splash.SplashFragment$onViewCreated.emit(SplashFragment.kt:38)
        at my.app.utils.UserInfo$getUserToken$$inlined$map.emit(Emitters.kt:224)
        at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun.invoke(SafeCollector.kt:15)
        at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun.invoke(SafeCollector.kt:15)
        at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:77)
        at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:59)
        at androidx.datastore.core.SingleProcessDataStore$data$invokeSuspend$$inlined$map.emit(Collect.kt:137)
        at kotlinx.coroutines.flow.FlowKt__LimitKt$dropWhile.emit(Limit.kt:37)
        at kotlinx.coroutines.flow.StateFlowImpl.collect(StateFlow.kt:398)
        at kotlinx.coroutines.flow.StateFlowImpl$collect.invokeSuspend(Unknown Source:15)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)

RegisterFragment代码:

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //InitViews
        binding.apply {
            //Submit
            submitBtn.setOnClickListener { it ->
                val name = nameEdt.text.toString()
                val email = emailEdt.text.toString()
                val password = passwordEdt.text.toString()
                //Validation
                if (name.isNotEmpty() || email.isNotEmpty() || password.isNotEmpty()) {
                    body.name = name
                    body.email = email
                    body.password = password

                    viewModel.registerUser(body)

                    viewModel.registerUser.observe(viewLifecycleOwner) { itResponse ->
                        Log.e("UserInfoLog","1 : "+itResponse.email.toString())
                        lifecycle.coroutineScope.launchWhenCreated {
                            userDataStore.saveUserToken(itResponse.email.toString())
                            Log.e("UserInfoLog","2 : "+itResponse.email.toString())
                            findNavController().navigateUp()
                        }
                    }

                } else {
                    Snackbar.make(it, getString(R.string.fillAllFields), Snackbar.LENGTH_SHORT).show()
                }

SplashFragment 代码:

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //Check user token
        lifecycle.coroutineScope.launchWhenCreated {
            delay(2000)

            userDataStore.getUserToken().collect {
                if (it.isEmpty()) {
                    findNavController().navigate(R.id.actionSplashToRegister)
                } else {
                    findNavController().navigate(R.id.actionSplashToHome)
                }
            }
        }
    }

导航代码:

<?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/nav_main"
    app:startDestination="@id/splashFragment">

    <fragment
        android:id="@+id/splashFragment"
        android:name="my.app.ui.splash.SplashFragment"
        android:label="fragment_splash"
        tools:layout="@layout/fragment_splash">
        <action
            android:id="@+id/actionSplashToRegister"
            app:destination="@id/registerFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim" />
        <action
            android:id="@+id/actionSplashToHome"
            app:destination="@id/homeFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim"/>
    </fragment>
    <fragment
        android:id="@+id/registerFragment"
        android:name="my.app.ui.register.RegisterFragment"
        android:label="fragment_register"
        tools:layout="@layout/fragment_register">
        <action
            android:id="@+id/actionRegisterToHome"
            app:destination="@id/homeFragment" />
    </fragment>
    <fragment
        android:id="@+id/homeFragment"
        android:name="my.app.ui.home.HomeFragment"
        android:label="fragment_home"
        tools:layout="@layout/fragment_home">
        <action
            android:id="@+id/action_homeFragment_to_detailFragment"
            app:destination="@id/detailFragment" />
    </fragment>
    <fragment
        android:id="@+id/favoriteFragment"
        android:name="my.app.ui.favorite.FavoriteFragment"
        android:label="fragment_favorite"
        tools:layout="@layout/fragment_favorite" />
    <fragment
        android:id="@+id/searchFragment"
        android:name="my.app.ui.search.SearchFragment"
        android:label="fragment_search"
        tools:layout="@layout/fragment_search" />
    <fragment
        android:id="@+id/detailFragment"
        android:name="my.app.ui.detail.DetailFragment"
        android:label="fragment_detail"
        tools:layout="@layout/fragment_detail" />
</navigation>

我该如何解决?

看起来 findController() 出于某种原因仍在使用 RegisterFragment 的目的地。

我猜根本原因是因为线程混淆了 findController(),这使得它仍然认为你在 RegisterFragment

作为深入研究线程管理之前的解决方法,您可以尝试对导航布局文件中的 Global Action 执行 actionSplashToHome 操作。只需将该操作移到 fragment 标记之外,然后此导航文件中的任何片段都可以使用此操作。

<?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/nav_main"
    app:startDestination="@id/splashFragment">

    <fragment
        android:id="@+id/splashFragment"
        android:name="my.app.ui.splash.SplashFragment"
        android:label="fragment_splash"
        tools:layout="@layout/fragment_splash">

        <action
            android:id="@+id/actionSplashToRegister"
            app:destination="@id/registerFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim" />
    </fragment>

     <action
        android:id="@+id/action_global_splash_ToHome"
        app:destination="@id/homeFragment"
        app:enterAnim="@anim/nav_default_enter_anim"
        app:exitAnim="@anim/nav_default_exit_anim"
        app:popEnterAnim="@anim/nav_default_pop_enter_anim"
        app:popExitAnim="@anim/nav_default_pop_exit_anim"/>

    <fragment
        android:id="@+id/registerFragment"
        android:name="my.app.ui.register.RegisterFragment"
        android:label="fragment_register"
        tools:layout="@layout/fragment_register">
        <action
            android:id="@+id/actionRegisterToHome"
            app:destination="@id/homeFragment" />
    </fragment>
    <fragment
        android:id="@+id/homeFragment"
        android:name="my.app.ui.home.HomeFragment"
        android:label="fragment_home"
        tools:layout="@layout/fragment_home">
        <action
            android:id="@+id/action_homeFragment_to_detailFragment"
            app:destination="@id/detailFragment" />
    </fragment>
    <fragment
        android:id="@+id/favoriteFragment"
        android:name="my.app.ui.favorite.FavoriteFragment"
        android:label="fragment_favorite"
        tools:layout="@layout/fragment_favorite" />
    <fragment
        android:id="@+id/searchFragment"
        android:name="my.app.ui.search.SearchFragment"
        android:label="fragment_search"
        tools:layout="@layout/fragment_search" />
    <fragment
        android:id="@+id/detailFragment"
        android:name="my.app.ui.detail.DetailFragment"
        android:label="fragment_detail"
        tools:layout="@layout/fragment_detail" />
</navigation>

无需更改 Splashfragment 中的代码,仍然使用 findNavController().navigate(R.id.action_global_splash_ToHome)