Android Navcontroller hardware back button crash

2019-08-16 18:32发布

问题:

I am using new android storyboard to create an application. The flow need to be like following:

SplashFragment -> Fragment1 -> Fragment2

Following is the storyboard(navigation_main.xml):

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

    <fragment android:id="@+id/splashFragment" android:name="com.myapp.android.SplashFragment"
              android:label="fragment_splash" tools:layout="@layout/fragment_splash">
        <action android:id="@+id/action_splashFragment_to_fragment1"
                app:destination="@id/fragment1"/>
    </fragment>
    <fragment android:id="@+id/fragment1"
              android:name="com.myapp.android.Fragment1"
              android:label="fragment1" tools:layout="@layout/fragment_register_msisdn">
        <action android:id="@+id/action_fragment1_to_fragment2"
                app:destination="@id/fragment2" app:popUpTo="@+id/fragment1"
                app:enterAnim="@anim/nav_default_pop_enter_anim" app:exitAnim="@anim/nav_default_pop_exit_anim"/>
    </fragment>
    <fragment android:id="@+id/fragment2"
              android:name="com.myapp.android.Fragment2"
              android:label="fragment_fragment2" tools:layout="@layout/fragment_fragment2"/>
</navigation>

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="MainActivity">

    <fragment
            android:id="@+id/mainNavigationHostFragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:defaultNavHost="true"
            app:navGraph="@navigation/navigation_main" />

</androidx.constraintlayout.widget.ConstraintLayout>

App Theme is withou t action bar since I don't want the actionbar to show up:

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">

Basically I need a Fragment1 to Fragment2 navigation and then on hardware back button press, back to Fragment1. To navigate from Fragment1 to Fragment2, I have the following code in Fragment1:

findNavController().navigate(R.id.action_fragment1_to_fragment2)

SplashFragment should not be maintained in the stack, since it is not required after first time showing in launch. That is why I have popTo only in Fragment1 to Fragment2 action. But after running the same, pressing back from Fragment2, for the first time does nothing(doesn't pop) and the second time, it crashes with the following exception:

2019-04-25 16:52:43.841 28598-28598/com.selfcare.safaricom E/InputEventSender: Exception dispatching finished signal.
2019-04-25 16:52:43.842 28598-28598/com.selfcare.safaricom E/MessageQueue-JNI: Exception in MessageQueue callback: handleReceiveCallback
2019-04-25 16:52:43.846 28598-28598/com.selfcare.safaricom E/MessageQueue-JNI: java.lang.IllegalArgumentException: navigation destination com.selfcare.safaricom:id/action_splashFragment_to_registerMSISDNFragment is unknown to this NavController
        at androidx.navigation.NavController.navigate(NavController.java:803)
        at androidx.navigation.NavController.navigate(NavController.java:744)
        at androidx.navigation.NavController.navigate(NavController.java:730)
        at androidx.navigation.NavController.navigate(NavController.java:718)
        at com.myapp.android.SplashFragment.handleLaunchStatus(SplashFragment.kt:51)
        at com.myapp.android.SplashFragment.access$handleLaunchStatus(SplashFragment.kt:16)
        at com.myapp.android.SplashFragment$attachLaunchObserver$1.onChanged(SplashFragment.kt:44)
        at com.myapp.android.SplashFragment$attachLaunchObserver$1.onChanged(SplashFragment.kt:16)
        at androidx.lifecycle.LiveData.considerNotify(LiveData.java:113)
        at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:126)
        at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:424)
        at androidx.lifecycle.LiveData$LifecycleBoundObserver.onStateChanged(LiveData.java:376)
        at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:361)
        at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:188)
        at androidx.lifecycle.LiveData.observe(LiveData.java:185)
        at com.myapp.android.SplashFragment.attachLaunchObserver(SplashFragment.kt:43)
        at com.myapp.android.SplashFragment.onViewCreated(SplashFragment.kt:35)
        at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:895)
        at androidx.fragment.app.FragmentManagerImpl.addAddedFragments(FragmentManagerImpl.java:2092)
        at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1866)
        at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1822)
        at androidx.fragment.app.FragmentManagerImpl.popBackStackImmediate(FragmentManagerImpl.java:298)
        at androidx.fragment.app.FragmentManagerImpl.popBackStackImmediate(FragmentManagerImpl.java:241)
        at androidx.fragment.app.FragmentManagerImpl.popBackStackImmediate(FragmentManagerImpl.java:288)
        at androidx.fragment.app.FragmentManagerImpl.popBackStackImmediate(FragmentManagerImpl.java:241)
        at androidx.fragment.app.FragmentActivity$1.handleOnBackPressed(FragmentActivity.java:144)
        at androidx.activity.OnBackPressedDispatcher.onBackPressed(OnBackPressedDispatcher.java:136)
        at androidx.activity.ComponentActivity.onBackPressed(ComponentActivity.java:283)
        at android.app.Activity.onKeyUp(Activity.java:3083)
        at android.view.KeyEvent.dispatch(KeyEvent.java:2716)
        at android.app.Activity.dispatchKeyEvent(Activity.java:3366)
        at androidx.core.app.ComponentActivity.superDispatchKeyEvent(ComponentActivity.java:80)
        at androidx.core.view.KeyEventDispatcher.dispatchKeyEvent(KeyEventDispatcher.java:84)
        at androidx.core.app.ComponentActivity.dispatchKeyEvent(ComponentActivity.java:98)
        at androidx.appcompat.app.AppCompatActivity.dispatchKeyEvent(AppCompatActivity.java:558)
        at androidx.appcompat.view.WindowCallbackWrapper.dispatchKeyEvent(WindowCallbackWrapper.java:59)
        at androidx.appcompat.app.AppCompatDelegateImpl$AppCompatWindowCallback.dispatchKeyEvent(AppCompatDelegateImpl.java:2736)
        at com.android.internal.policy.DecorView.dispatchKeyEvent(DecorView.java:342)
        at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:5037)
        at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4905)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4426)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4479)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4445)
        at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4585)
        at android.view.ViewRootImpl$InputStage.apply(Vie
2019-04-25 16:52:43.849 28598-28598/com.selfcare.safaricom E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.selfcare.safaricom, PID: 28598
    java.lang.IllegalArgumentException: navigation destination com.selfcare.safaricom:id/action_splashFragment_to_registerMSISDNFragment is unknown to this NavController
        at androidx.navigation.NavController.navigate(NavController.java:803)
        at androidx.navigation.NavController.navigate(NavController.java:744)
        at androidx.navigation.NavController.navigate(NavController.java:730)
        at androidx.navigation.NavController.navigate(NavController.java:718)
        at com.myapp.android.SplashFragment.handleLaunchStatus(SplashFragment.kt:51)
        at com.myapp.android.SplashFragment.access$handleLaunchStatus(SplashFragment.kt:16)
        at com.myapp.android.SplashFragment$attachLaunchObserver$1.onChanged(SplashFragment.kt:44)
        at com.myapp.android.SplashFragment$attachLaunchObserver$1.onChanged(SplashFragment.kt:16)
        at androidx.lifecycle.LiveData.considerNotify(LiveData.java:113)
        at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:126)
        at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:424)
        at androidx.lifecycle.LiveData$LifecycleBoundObserver.onStateChanged(LiveData.java:376)
        at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:361)
        at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:188)
        at androidx.lifecycle.LiveData.observe(LiveData.java:185)
        at com.myapp.android.SplashFragment.attachLaunchObserver(SplashFragment.kt:43)
        at com.myapp.android.SplashFragment.onViewCreated(SplashFragment.kt:35)
        at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:895)
        at androidx.fragment.app.FragmentManagerImpl.addAddedFragments(FragmentManagerImpl.java:2092)
        at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1866)
        at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1822)
        at androidx.fragment.app.FragmentManagerImpl.popBackStackImmediate(FragmentManagerImpl.java:298)
        at androidx.fragment.app.FragmentManagerImpl.popBackStackImmediate(FragmentManagerImpl.java:241)
        at androidx.fragment.app.FragmentManagerImpl.popBackStackImmediate(FragmentManagerImpl.java:288)
        at androidx.fragment.app.FragmentManagerImpl.popBackStackImmediate(FragmentManagerImpl.java:241)
        at androidx.fragment.app.FragmentActivity$1.handleOnBackPressed(FragmentActivity.java:144)
        at androidx.activity.OnBackPressedDispatcher.onBackPressed(OnBackPressedDispatcher.java:136)
        at androidx.activity.ComponentActivity.onBackPressed(ComponentActivity.java:283)
        at android.app.Activity.onKeyUp(Activity.java:3083)
        at android.view.KeyEvent.dispatch(KeyEvent.java:2716)
        at android.app.Activity.dispatchKeyEvent(Activity.java:3366)
        at androidx.core.app.ComponentActivity.superDispatchKeyEvent(ComponentActivity.java:80)
        at androidx.core.view.KeyEventDispatcher.dispatchKeyEvent(KeyEventDispatcher.java:84)
        at androidx.core.app.ComponentActivity.dispatchKeyEvent(ComponentActivity.java:98)
        at androidx.appcompat.app.AppCompatActivity.dispatchKeyEvent(AppCompatActivity.java:558)
        at androidx.appcompat.view.WindowCallbackWrapper.dispatchKeyEvent(WindowCallbackWrapper.java:59)
        at androidx.appcompat.app.AppCompatDelegateImpl$AppCompatWindowCallback.dispatchKeyEvent(AppCompatDelegateImpl.java:2736)
        at com.android.internal.policy.DecorView.dispatchKeyEvent(DecorView.java:342)
        at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:5037)
        at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4905)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4426)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4479)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4445)
2019-04-25 16:52:43.851 28598-28598/com.selfcare.safaricom E/AndroidRuntime:     at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4585)
        at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4453)
        at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4642)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4426)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4479)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4445)
        at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4453)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4426)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4479)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4445)
        at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4618)
        at android.view.ViewRootImpl$ImeInputStage.onFinishedInputEvent(ViewRootImpl.java:4779)
        at android.view.inputmethod.InputMethodManager$PendingEvent.run(InputMethodManager.java:2571)
        at android.view.inputmethod.InputMethodManager.invokeFinishedInputEventCallback(InputMethodManager.java:2081)
        at android.view.inputmethod.InputMethodManager.finishedInputEvent(InputMethodManager.java:2072)
        at android.view.inputmethod.InputMethodManager$ImeInputEventSender.onInputEventFinished(InputMethodManager.java:2548)
        at android.view.InputEventSender.dispatchInputEventFinished(InputEventSender.java:141)
        at android.os.MessageQueue.nativePollOnce(Native Method)
        at android.os.MessageQueue.next(MessageQueue.java:326)
        at android.os.Looper.loop(Looper.java:160)
        at android.app.ActivityThread.main(ActivityThread.java:6718)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

SplashFragment line 51 is:

findNavController().navigate(R.id.action_splashFragment_to_fragment1)

This exception is gone if I remove the popTo from Fragment1 to Fragment2 action, but then also the back button doesn't work. What am I doing wrong here?

Edit 1:

As per Stavro Xhardha's comment, I made some modification to the navigation XML:

<fragment android:id="@+id/splashFragment" android:name="com.myapp.android.SplashFragment"
          android:label="fragment_splash" tools:layout="@layout/fragment_splash">
    <action android:id="@+id/action_splashFragment_to_fragment1"
            app:destination="@id/fragment1"
    app:popUpToInclusive="true" app:popUpTo="@+id/splashFragment"/> <!--Added this line -->
</fragment>
<fragment android:id="@+id/fragment1"
          android:name="com.myapp.android.Fragment1"
          android:label="fragment1" tools:layout="@layout/fragment_register_msisdn">
    <action android:id="@+id/action_fragment1_to_fragment2"
            app:destination="@id/fragment2" app:popUpTo="@+id/fragment1"/>
</fragment>
<fragment android:id="@+id/fragment2"
          android:name="com.myapp.android.Fragment2"
          android:label="fragment_fragment2" tools:layout="@layout/fragment_fragment2"/>

And in MainActivity override onBackPressed as follows:

override fun onBackPressed() {
    super.onBackPressed()
    if (!findNavController(R.id.launchNavigationHostFragment).navigateUp()) {
        finish()
    }
}

Now Fragment2 pops to Fragment1, but subsequent back press on Fragment1 keeps on bringing Fragment1 in a loop. I am not able to exit the app.

回答1:

I finally managed to figure out the issue and solved it.

The issue was that, I was observing a MutableLiveData from ViewModel and based on its value the navigation was happening. But I was not aware that the life cycle owner of fragment tends to destroy the Observer and reinstantiate it based on view life cycle to avoid leaks. Hence once the navigation happens, the Observer is no longer there and the code to navigate is within the observer. The same code is required on navigating back, hence when trying to access it, the code crashes.

I solved the issue by using an interface to give callback to fragment when the navigation needs to be done.



回答2:

Your error log is saying that you are calling an action with navcontroller with action id action_splashFragment_to_registerMSISDNFragment. Try to find that action id and check if it is valid or not. Also use app:popUpTo="@id/splashFragment" app:popUpToInclusive="true" in action_splashFragment_to_fragment1 instead of action_fragment1_to_fragment2. This will remove splash fragment from the backstack. Here is the code snippet:

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

        <fragment android:id="@+id/splashFragment" android:name="com.myapp.android.SplashFragment"
                  android:label="fragment_splash" tools:layout="@layout/fragment_splash">
            <action android:id="@+id/action_splashFragment_to_fragment1"
                    app:destination="@id/fragment1"
                app:popUpTo="@id/splashFragment"
                app:popUpToInclusive="true" />
        </fragment>
        <fragment android:id="@+id/fragment1"
                  android:name="com.myapp.android.Fragment1"
                  android:label="fragment1" tools:layout="@layout/fragment_register_msisdn">
            <action android:id="@+id/action_fragment1_to_fragment2"
                    app:destination="@id/fragment2"
                    app:enterAnim="@anim/nav_default_pop_enter_anim" app:exitAnim="@anim/nav_default_pop_exit_anim"/>
        </fragment>
        <fragment android:id="@+id/fragment2"
                  android:name="com.myapp.android.Fragment2"
                  android:label="fragment_fragment2" tools:layout="@layout/fragment_fragment2"/>
    </navigation>


回答3:

I just removed the observer after navigating to a new fragment.Like this:

myLiveData.removeObservers(this)