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