Home > Enterprise >  How to open new fragment with NavigationComponent on Android
How to open new fragment with NavigationComponent on Android

Time:03-11

In my application I used single activity and use some fragments!
I want first of all show SplashFragment and checked user token, if exists open HomeFragment else open RegisterFragment!

I write below codes, but after register user show me below error!

Logcat error :

    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$1$1.emit(SplashFragment.kt:43)
        at my.app.ui.splash.SplashFragment$onViewCreated$1$1.emit(SplashFragment.kt:38)
        at my.app.utils.UserInfo$getUserToken$$inlined$map$1$2.emit(Emitters.kt:224)
        at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt:15)
        at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.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$1$invokeSuspend$$inlined$map$1$2.emit(Collect.kt:137)
        at kotlinx.coroutines.flow.FlowKt__LimitKt$dropWhile$1$1.emit(Limit.kt:37)
        at kotlinx.coroutines.flow.StateFlowImpl.collect(StateFlow.kt:398)
        at kotlinx.coroutines.flow.StateFlowImpl$collect$1.invokeSuspend(Unknown Source:15)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)

RegisterFragment codes:

    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 codes:

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

Navigation's codes:

<?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>

How can I fix it?

CodePudding user response:

Looks like findController() is still using the destination of RegisterFragment for some reason.

My guess the root cause is because the thread confuses findController() which makes it still think you are at RegisterFragment.

As a workaround fix before digging too deep into thread management, you can try to make action actionSplashToHome to a Global Action in your navigation layout file. Just move that action outside the fragment tag, then any fragment in this navigation file can use this action.

<?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>

No need to change your code in Splashfragment, still use findNavController().navigate(R.id.action_global_splash_ToHome)

  • Related