androidkotlinnavigationandroid-jetpack-navigation

Android Jetpack Navigation Library (Navigation State breaks on Configuration Changes)


I'm implementing Single Activity Architecture on a native Android Kotlin app, with a CoreActivity hosting a single navigation host, which manages the navigation for the app.

I'm using Jetpack Navigation to handle the navigation state with a core navigation graph file to describe the nav heirarchy. https://developer.android.com/jetpack/androidx/releases/navigation

The PROBLEM

When trying to navigate between 2 fragments (before configuration changes), it works fine.

However if let's say the orientation changes, the fragment manager on the navigation library simply refuses to navigate and outputs the following when running against LogCat.

FragmentNavigator: Ignoring navigate() call: FragmentManager has already saved its state

UPDATE: So according to @Pratik Prakash Bindage answer, I have to run some logic on the onActivityResult to restore the navigation State and prevent the Fragment Manager from locking up.

The problem is I can't find any documentation or sample code on how to do this.

Can anyone post any sample code on how to do this, if you've already solved this problem? Thanks

Also why invoke the base code onActivityResult?

I'm not navigating between activities, I'm only navigating between fragments, on a single NavHost. I'm never moving between activities, so I'm not entirely sure why invoke base code, which is invoked anyway, by the runtime. Why override a method, if I have no business logic to run there?

JUST TO BE CLEAR

I'm Using Classical Android (XML) based. I'm not using Compose

UPDATE

I found the answer. Turns out it was just how my navigation system worked. I was referring to a acitvity context (when running navigation) that no longer existed after a configuration change.

Then when the navigation service was invoking navigate function, it was invoking from an activity that no longer existed (hence the fragment manager couldn't do anything).

So it was just a bug on my end. I thought for a second it might be a bug on the Navigation Jetpack library, but I wanted to check here to be sure, before an issue on their git.

All good. Thanks


Solution

  • These are a few helpful links that will help us learn more....

    1. Navigation : https://developer.android.com/guide/navigation

    2. Navigating in Jetpack Compose : https://medium.com/google-developer-experts/navigating-in-jetpack-compose-78c78d365c6a

    3. Design your navigation graph : https://developer.android.com/guide/navigation/design

    4. Fragment Navigator https://developer.android.com/reference/androidx/navigation/fragment/FragmentNavigator

    One solution to this problem is to use the FragmentNavigator.Extras class, which allows the navigation library to continue safe navigation even after a configuration update.

    1. First, build a FragmentNavigator object.Extras, and pass in the shared element transitions you want to use.

      val extras = FragmentNavigatorExtras(
        transitionView1 to "transitionName1",
        transitionView2 to "transitionName2"
      )
      

    In this instance, transitionView1 & transitionView2 is the view that will be used as a shared element transition.transitionName1 and transitionName2 is the transition name you've given it.

    1. Next, use the NavController's navigate() method to go to the new fragment while passing in the FragmentNavigator

      findNavController().navigate(R.id.newFragment, null, null, extras)
      

    In this case, R.id.newFragment is the ID of the new fragment to which you want to go.

    1. The new fragment's onCreateView() method should use the findViewById() method to find a reference to the shared element views and specify their transition names.

      override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
       val view = inflater.inflate(R.layout.new_fragment, container, false)
       val transitionView1 = view.findViewById<View>(R.id.transitionView1)
       val transitionView2 = view.findViewById<View>(R.id.transitionView2)
       TransitionManager.beginDelayedTransition(container, transition)
       transitionView1.transitionName = "transitionName1"
       transitionView2.transitionName = "transitionName2"
       return view
      }