Status Update
Comments
il...@google.com <il...@google.com>
ap...@google.com <ap...@google.com> #2
Please include a sample project using Navigation 2.4.0-rc01 that reproduces your issue.
il...@google.com <il...@google.com> #3
Thank you but this version can still be reproduced,here is the project that can be reproduced:
- enable the setting of "don't keep activity" in developer options.
- start app, click "About" button.
- press HOME button.
- click app icon in launcher again.
ap...@google.com <ap...@google.com> #4
Thanks, the sample project was really helpful. While the behavior here could be considered 'technically correct', this is unintentional and we'll look to see what we can do to improve this case.
What's actually going on
This looks like a case where there's a marked difference in behavior between fragment lifecycle callbacks and
Specifically, there are three elements in play:
- The
NavHostFragment
- The
NavController
and theNavBackStackEntry
for each entry in its back stack - The child
Fragment
for each entry in the back stack that corresponds with a fragment destination
When you first load your activity, the NavHostFragment
is in its initial state (INITIALIZED
in the AndroidX Lifecycle terms). In the NavHostFragment
's onCreate()
, it sets the graph you've set via the app:navGraph="@navigation/nav_graph"
in your activity_main.xml
on the NavController
it owns. This is what causes Fragments to be added to the NavHostFragment
's childFragmentManager
.
Because of how Fragments work currently, the fragment lifecycle callbacks for upward transitions (like onCreate()
) are called before the AndroidX Lifecycle is moved to that same state:
NavHostFragment
gets itsonCreate()
calledNavHostFragment
inflates its navigation graph and adds theTitle
fragment to thechildFragmentManager
- Fragments call
onCreate()
on their child fragments (i.e.,Title
) NavHostFragment
's Lifecycle is moved toCREATED
- Fragments move their child fragments to
CREATED
The same thing happens for onStart()
+STARTED
and onResume()
+RESUMED
.
This all works as expected when dealing with only Fragments - the parent fragment gets onCreate()
before its child fragments. That nesting is also done on the AndroidX Lifecycle side when it comes to the Fragment's Lifecycle. That idea of nesting Lifecycle changes between parent and child is a key part of the whole infrastructure. The goal is that at no point does a 'child' have a higher Lifecycle state as its parent.
NavController
depends solely on AndroidX Lifecycle to drive the Lifecycle of each NavBackStackEntry
. And where does that NavController
gets its AndroidX Lifecycle callbacks from? From the NavHostFragment
that it has been created by. This means that what is actually happening adds an additional line:
NavHostFragment
gets itsonCreate()
calledNavHostFragment
inflates its navigation graph and adds theTitle
fragment to thechildFragmentManager
- Fragments call
onCreate()
on their child fragments (i.e.,Title
) NavHostFragment
's Lifecycle is moved toCREATED
NavController
moves eachNavBackStackEntry
toCREATED
- Fragments move their child fragments to
CREATED
This is where your error message comes in: you're accessing a NavBackStackEntry
's ViewModel in step 3 which is before it actually moves to CREATED
(that's step 5).
Caused by: java.lang.IllegalStateException: You cannot access the NavBackStackEntry's ViewModels until it is added to the NavController's back stack (i.e., the Lifecycle of the NavBackStackEntry reaches the CREATED state).
You'd find you'd get the same exact error message if you moved your by navGraphViewModels()
code to the Title
fragment. You just also see the same issue after turning on 'Don't keep activities' since when recreating the entire activity, all child fragments (including both Title
and About
) are going through these same steps of having their onCreate()
called before the NavHostFragment
has had its AndroidX Lifecycle moved to CREATED
. This is also explains why it works when you navigate
to About
manually - by that point, the NavHostFragment
is RESUMED
and each NavBackStackEntry
is able to move beyond the CREATED
state immediately as part of that call to navigate()
.
Workarounds
So if you want to run your code after the NavController
moves each NavBackStackEntry
to CREATED
, you'll want to move your code to step 6 and use an LifecycleObserver
.
Using the DefaultLifecycleObserver
class is by far the easiest way to do this. By upgrading to Lifecycle 2.4.0
(by updating the version in versions.gradle
and adding an explicit dependency on implementation deps.lifecycle.runtime
), you could rewrite your About
fragment as:
class About : Fragment(), DefaultLifecycleObserver {
private val vm by navGraphViewModels<TestViewModel>(R.id.home)
// Note how this has a different signature from the Fragment method of the same name
// this signature is the one from DefaultLifecycleObserver
override fun onCreate(owner: LifecycleOwner) {
Log.d("About", "About string ${vm.name}")
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_about, container, false)
}
}
By moving to AndroidX Lifecycle callbacks, you ensure that your child fragment calls always happen after the parent NavHostFragment
and its NavController
have moved through that same Lifecycle state.
Next steps on our side
As per the error message, the intent is that you cannot access the ViewModels until the NavBackStackEntry
has been added to the NavController
's back stack. However, in this case, the NavBackStackEntry
is on the back stack (that's why by navGraphViewModels()
was able to find the NavBackStackEntry
associated with R.id.home
in the first place - you wouldn't have been able to get to this error message if you had chosen an ID that didn't exist on the back stack).
Therefore there might be more clever logic we can do to cover the 'added to back stack but not yet CREATED
' case to allow for this type of call from a fragment's onCreate()
to succeed. I'll leave this issue open to cover exactly that work.
Future state
There's ongoing work being done in onCreate()
would only occur when your AndroidX Lifecycle has moved to CREATED
, effectively removing the difference between the two types of callbacks. I'd strongly suggest staring that issue as well to track when that work will be available.
ap...@google.com <ap...@google.com> #5
Thanks, I've learnt a lot and you save my life, I'll keep an eye on this issue until there're any updates.
ap...@google.com <ap...@google.com> #6
Branch: androidx-main
commit 2560e8fa99d8e13c5907375e69759520fefc8dd5
Author: Ian Lake <ilake@google.com>
Date: Wed Jan 19 00:36:30 2022
Fix getViewModelStore() prior to ON_CREATE
The intention is that the ViewModels associated
with a NavBackStackEntry cannot be accessed prior
to the NavBackStackEntry being added to the
NavController. We were previously using the ON_CREATE
event to signify when this happens, but in fact there are
cases, such as in a Fragment's onCreate(), where a
NavBackStackEntry can be retrieved from the NavController
*before* the Lifecycle reaches CREATED (due to how
fragment lifecycle callbacks relate to Lifecycle changes).
By performing the restore exactly once the first time
updateState() is called, we ensure that ViewModels are
accessible immediately after being added to the NavController.
Test: updated test suites
BUG: 213504272
Relnote: "Fixed an issue where accessing a ViewModel created
via `by navGraphViewModels()` from a Fragment's `onCreate()`
would fail with an `IllegalStateException`."
Change-Id: I8a14dd596195d4ddfa8199c8023a7aedcd286113
M navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavBackStackEntryTest.kt
M navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt
M navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavHostFragmentTest.kt
ap...@google.com <ap...@google.com> #7
We've fixed this issue internally and it will be available in the upcoming Navigation 2.5.0-alpha01 and Navigation 2.4.1 releases.
ap...@google.com <ap...@google.com> #8
Branch: snap-temp-L34200000699848945
commit 0da3ed71fa5545646d44fb44219b31e7e5621f8f
Author: Ian Lake <ilake@google.com>
Date: Mon Sep 14 16:10:06 2020
Mention the current destination in getBackStackEntry errors
Improve the debugging experience for "No destination
with ID..." errors in getBackStackEntry() by mentioning
exactly destination the NavController is on. This would
allow developers to know if they have called popBackStack()
to a destination they don't expect to be at or if
they have not called setGraph() at all (where the
current destination would be null).
Test: existing tests pass
BUG: 168311416
Change-Id: Ia96b6732d7caadbddf31b7f35a1a419407ff6e25
(cherry picked from commit d0a10be065a3a629e3835f28d35804c91d8d2e4d)
M navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
ap...@google.com <ap...@google.com> #9
Branch: snap-temp-L04700000699849955
commit 12b13608ec1155b5780fe44f5ab07cdc93c5ba90
Author: Ian Lake <ilake@google.com>
Date: Mon Sep 14 16:10:06 2020
Mention the current destination in getBackStackEntry errors
Improve the debugging experience for "No destination
with ID..." errors in getBackStackEntry() by mentioning
exactly destination the NavController is on. This would
allow developers to know if they have called popBackStack()
to a destination they don't expect to be at or if
they have not called setGraph() at all (where the
current destination would be null).
Test: existing tests pass
BUG: 168311416
Change-Id: Ia96b6732d7caadbddf31b7f35a1a419407ff6e25
(cherry picked from commit d0a10be065a3a629e3835f28d35804c91d8d2e4d)
M navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
ap...@google.com <ap...@google.com> #10
Branch: snap-temp-L69500000699869549
commit 7204d04ad74baf9816389e27778266391fbd79a5
Author: Ian Lake <ilake@google.com>
Date: Mon Sep 14 16:10:06 2020
Mention the current destination in getBackStackEntry errors
Improve the debugging experience for "No destination
with ID..." errors in getBackStackEntry() by mentioning
exactly destination the NavController is on. This would
allow developers to know if they have called popBackStack()
to a destination they don't expect to be at or if
they have not called setGraph() at all (where the
current destination would be null).
Test: existing tests pass
BUG: 168311416
Change-Id: Ia96b6732d7caadbddf31b7f35a1a419407ff6e25
(cherry picked from commit d0a10be065a3a629e3835f28d35804c91d8d2e4d)
M navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
ap...@google.com <ap...@google.com> #11
Branch: snap-temp-L31300000699869852
commit 398a1fe7bb7792589189574aec5f6e77bb749229
Author: Ian Lake <ilake@google.com>
Date: Mon Sep 14 16:10:06 2020
Mention the current destination in getBackStackEntry errors
Improve the debugging experience for "No destination
with ID..." errors in getBackStackEntry() by mentioning
exactly destination the NavController is on. This would
allow developers to know if they have called popBackStack()
to a destination they don't expect to be at or if
they have not called setGraph() at all (where the
current destination would be null).
Test: existing tests pass
BUG: 168311416
Change-Id: Ia96b6732d7caadbddf31b7f35a1a419407ff6e25
(cherry picked from commit d0a10be065a3a629e3835f28d35804c91d8d2e4d)
M navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
ap...@google.com <ap...@google.com> #12
Branch: snap-temp-L53700000699921463
commit 31027c16ff20b10e78093f8de205c81fc49dfc92
Author: Ian Lake <ilake@google.com>
Date: Mon Sep 14 16:10:06 2020
Mention the current destination in getBackStackEntry errors
Improve the debugging experience for "No destination
with ID..." errors in getBackStackEntry() by mentioning
exactly destination the NavController is on. This would
allow developers to know if they have called popBackStack()
to a destination they don't expect to be at or if
they have not called setGraph() at all (where the
current destination would be null).
Test: existing tests pass
BUG: 168311416
Change-Id: Ia96b6732d7caadbddf31b7f35a1a419407ff6e25
(cherry picked from commit d0a10be065a3a629e3835f28d35804c91d8d2e4d)
M navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
ap...@google.com <ap...@google.com> #13
Branch: snap-temp-L25200000699921867
commit 63e58ff4f522eb70dec6c0749679fb6b963698d8
Author: Ian Lake <ilake@google.com>
Date: Mon Sep 14 16:10:06 2020
Mention the current destination in getBackStackEntry errors
Improve the debugging experience for "No destination
with ID..." errors in getBackStackEntry() by mentioning
exactly destination the NavController is on. This would
allow developers to know if they have called popBackStack()
to a destination they don't expect to be at or if
they have not called setGraph() at all (where the
current destination would be null).
Test: existing tests pass
BUG: 168311416
Change-Id: Ia96b6732d7caadbddf31b7f35a1a419407ff6e25
(cherry picked from commit d0a10be065a3a629e3835f28d35804c91d8d2e4d)
M navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
ap...@google.com <ap...@google.com> #14
Branch: snap-temp-L07700000699933785
commit 56fe0d47de20fb430c4280332a20eb96076dd44b
Author: Ian Lake <ilake@google.com>
Date: Mon Sep 14 16:10:06 2020
Mention the current destination in getBackStackEntry errors
Improve the debugging experience for "No destination
with ID..." errors in getBackStackEntry() by mentioning
exactly destination the NavController is on. This would
allow developers to know if they have called popBackStack()
to a destination they don't expect to be at or if
they have not called setGraph() at all (where the
current destination would be null).
Test: existing tests pass
BUG: 168311416
Change-Id: Ia96b6732d7caadbddf31b7f35a1a419407ff6e25
(cherry picked from commit d0a10be065a3a629e3835f28d35804c91d8d2e4d)
M navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
vi...@gws.hotstar.com <vi...@gws.hotstar.com> #15
Caused by java.lang.IllegalArgumentException
No destination with ID 2131427956 is on the NavController's back stack in HiltNavGraphViewModelLazy.kt:49) , we are using androidx.hilt:hilt-navigation-fragment:1.0.0
androidx.navigation.NavController.getBackStackEntry (NavController.kt:2204)
androidx.hilt.navigation.fragment.HiltNavGraphViewModelLazyKt$hiltNavGraphViewModels$backStackEntry$2.invoke (HiltNavGraphViewModelLazyKt.java:49)
$special$$inlined$hiltNavGraphViewModels$4.invoke (HiltNavGraphViewModelLazy.kt:49)
is there any way to prevent it?
al...@gmail.com <al...@gmail.com> #16
Could we further improve this by indicating the name of the destination instead of just the ID number?
jb...@google.com <jb...@google.com> #17
If you use a resource to provide the name it will already be displayed. Along with any route or label you may have added.
sh...@gmail.com <sh...@gmail.com> #18
Is there any way to safeguard against this? I am not able to reproduce the error myself, but get a lot of errors show up in Crashlytics
wm...@gmail.com <wm...@gmail.com> #19
ze...@gmail.com <ze...@gmail.com> #20
Same here. It is not fixed. Can you reopen and look again?
Description
Version used: 2.3.0
While using `by navGraphViewModels`, my crash reporting reports an error when invoked
NavGraphViewModelLazy.kt line 56
Fatal Exception: java.lang.IllegalArgumentException: No destination with ID 2131296839 is on the NavController's back stack
at androidx.navigation.NavController.getBackStackEntry(NavController.java:1293)
at com.example.ExampleFragment$$special$$inlined$navGraphViewModels$1.invoke(NavGraphViewModelLazy.kt:56)
[...]
This is difficult to debug since the navGraphId that I pass in should theoretically always be in the NavController's back stack when navGraphViewModels is invoked. The error message could be improved by letting me know what IS available on the back stack or if the back stack is completely empty.