13

When a configuration change happens and my Activity and Fragment are recreated because of it, my nav Graph scoped ViewModel is unavailable while the Fragments have already been created again.
It seems like the Fragment gets recreated, before the navGraph does.

I am using this code to initialize my navGraph scoped ViewModel from my Fragment:

private val myViewModel: MyViewModel by navGraphViewModels(R.id.nav_graph_id)

If I try to use myViewModel in the Fragments onViewCreated function, I get a IllegalArgumentException after a Configuration change. The Exception:

java.lang.IllegalArgumentException: No destination with ID <destination id> is on the NavController's back stack

How do I handle this?

I have already checked that my ID isn't used anywhere else.

Edit1:

Here is my activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        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:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
    <androidx.fragment.app.FragmentContainerView
            android:id="@+id/main_nav_host"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:defaultNavHost="true"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:navGraph="@navigation/main_nav_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>

And here is my main_nav_graph.xml:

<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/main_nav_graph"
        app:startDestination="@id/main_nav_graph_1">
    
    <navigation
            android:id="@+id/main_nav_graph_1"
            android:label="@string/nav_graph_1_label"
            app:startDestination="@id/nav_graph_1_start_fragment">
        <!-- nav graph stuff -->
    </navigation>
    
    <navigation
            android:id="@+id/main_nav_graph_2"
            android:label="@string/nav_graph_2_label"
            app:startDestination="@id/nav_graph_2_start_fragment">
        
        <fragment
                android:id="@+id/nav_graph_2_start_fragment"
                android:label="@string/nav_graph_2_start_fragment_label"
                android:name="my.package.ui.NavGraph2StartFragment"
                tools:layout="@layout/fragment_nav_graph_2_start">
        </fragment>
        <!-- In here is where I get the problem -->
    </navigation>
    
</navigation>
  • Are you using the `app:navGraph` field in XML? Or are you manually calling `setGraph` on your `NavController`? Can you include that code? – ianhanniballake Sep 07 '20 at 17:24
  • I have edited my post. Is this the code that you wanted? – AndroidKotlinNoob Sep 08 '20 at 07:17
  • What version of Navigation and Fragments are you using? – ianhanniballake Sep 08 '20 at 18:40
  • 2
    @ianhanniballake We've got the same problem and unfortunately the solution by AndroidKotlinNoob doesn't work for us. We start to observe the viewmodel in the onViewCreated() method and that does seem to be too early after activity recreation. The NavHostController contains the valid entries in the mBackStackToRestore property but the mBackStack array is still empty. We have to wait until the (deprecated) onActivityCreated() callback to make it work. Using Fragment 1.3.0-beta01, Activity 1.2.0-beta01 and Navigation 2.3.1. – harry248 Oct 26 '20 at 10:07
  • @harry248 I am using Navigation 2.3.1 and appcompat 1.2.0 and don't have that problem. Even if I update my appcompat library to 1.3.0-alpha02 it still works. Maybe you should make a seperate Post about this, because it will probably get more attention. – AndroidKotlinNoob Oct 27 '20 at 16:42
  • @AndroidKotlinNoob How did you fix this problem? – virengujariya Feb 15 '22 at 07:17
  • @harry248 thank you, moving initialization logic to `onStart()` fixes the problem for me – t3ddys May 19 '22 at 22:16

4 Answers4

5

My Problem was the following:

After a Configuration change the NavGraph returned to its start destination, but the Fragment that was last active gets loaded anyway. This meant that the Fragment that was actually started and the current destination of the navGraph were out of sync.
When my Fragment then tried to load the navGraph scoped ViewModel, it failed because the navGraph thought it was on a different Fragment then it actually was.

To fix my problem I had to save and restore the state of the navController using the activities savedInstanceState Bundle. (source: https://stackoverflow.com/a/59987336)

override fun onSaveInstanceState(savedInstanceState: Bundle) {
    super.onSaveInstanceState(savedInstanceState)
    savedInstanceState.putBundle("nav_state", fragment.findNavController().saveState())
}

// restore in RestoreInstanceState
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
    super.onRestoreInstanceState(savedInstanceState)
    fragment.findNavController().restoreState(savedInstanceState.getBundle("nav_state"))
}

// or restore in onCreate
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    if (savedInstanceState?.containsKey("nav_state") == true) {
        fragment.findNavController().restoreState(savedInstanceState.getBundle("nav_state"))
    }
}
5

For anyone else coming here, I experienced this issue when a dialog was currently displayed and I issued a navController.navigate() call to a Fragment which uses by navGraphViewModels() before the dialog was dismissed.

It's as if the Navigation library couldn't understand that there is a destination with that ID available when a dialog is being displayed.

And for more clarity, this dialog is displayed using the <dialog> attribute in my nav graph XML.

My fix (for now) is to ensure the dialog is dismissed before making a further call to navigate elsewhere by way of view.postDelayed({ navController.navigate(elsewhere)}, 300)

This isn't the first time I've experienced issues with the NavController trying to navigate when a dialog is displayed.

w3bshark
  • 2,700
  • 1
  • 30
  • 40
  • For me, I was using "by navGraphViewModels()" but I was providing a wrong nav id to it (was an id of a nav_graph that isn't in the dialog nav_graph) – Khaled Ahmed Mar 07 '23 at 21:40
1

My problem was that I defined the fragment with the same id in the nav graph and in an included nav graph.

user2424380
  • 1,393
  • 3
  • 16
  • 29
-6

Solved Easy by change this :

private val myViewModel: MyViewModel by hiltNavGraphViewModels(R.id.nav_graph_id)

To:

private val viewModel: AuthViewModel by activityViewModels{ defaultViewModelProviderFactory }
Leandro Bardelli
  • 10,561
  • 15
  • 79
  • 116