Status Update
Comments
il...@google.com <il...@google.com> #2
Please include a sample project using Navigation 2.4.0-rc01 that reproduces your issue.
ca...@gmail.com <ca...@gmail.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.
il...@google.com <il...@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.
jb...@google.com <jb...@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-L04700000699849955
commit a7aa9b8a2bd4a2f038b700d1ad57feea325ea137
Author: Jeremy Woods <jbwoods@google.com>
Date: Wed Sep 23 14:44:52 2020
Keep long arg names from wrapping
When using safe-args with a long argument name, kotlin does an
automatic line wrap at 100 characters which makes it place the argument
return and variable on different lines, which fails.
We should add `·` to emit a space that never wraps.
Test: Added KotlinNavWriterTest
Bug: 168584987
Change-Id: Ibc31f91fd6b1c4a94f0b7c05b9b9679ffa09b625
(cherry picked from commit 766a3ad47da73e1179ce6419693a825c4d665cfd)
M navigation/navigation-safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/kotlin/KotlinNavWriter.kt
M navigation/navigation-safe-args-generator/src/test/kotlin/androidx/navigation/safe/args/generator/KotlinNavWriterTest.kt
A navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongNameMainFragmentArgs.kt
ap...@google.com <ap...@google.com> #9
Branch: snap-temp-L69500000699869549
commit 658ded1d1c845cc764fb47e07c8f560ffae406ff
Author: Jeremy Woods <jbwoods@google.com>
Date: Wed Sep 23 14:44:52 2020
Keep long arg names from wrapping
When using safe-args with a long argument name, kotlin does an
automatic line wrap at 100 characters which makes it place the argument
return and variable on different lines, which fails.
We should add `·` to emit a space that never wraps.
Test: Added KotlinNavWriterTest
Bug: 168584987
Change-Id: Ibc31f91fd6b1c4a94f0b7c05b9b9679ffa09b625
(cherry picked from commit 766a3ad47da73e1179ce6419693a825c4d665cfd)
M navigation/navigation-safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/kotlin/KotlinNavWriter.kt
M navigation/navigation-safe-args-generator/src/test/kotlin/androidx/navigation/safe/args/generator/KotlinNavWriterTest.kt
A navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongNameMainFragmentArgs.kt
ap...@google.com <ap...@google.com> #10
Branch: snap-temp-L31300000699869852
commit 998afa5a45306f1096a053e0f9eb275481fe79ab
Author: Jeremy Woods <jbwoods@google.com>
Date: Wed Sep 23 14:44:52 2020
Keep long arg names from wrapping
When using safe-args with a long argument name, kotlin does an
automatic line wrap at 100 characters which makes it place the argument
return and variable on different lines, which fails.
We should add `·` to emit a space that never wraps.
Test: Added KotlinNavWriterTest
Bug: 168584987
Change-Id: Ibc31f91fd6b1c4a94f0b7c05b9b9679ffa09b625
(cherry picked from commit 766a3ad47da73e1179ce6419693a825c4d665cfd)
M navigation/navigation-safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/kotlin/KotlinNavWriter.kt
M navigation/navigation-safe-args-generator/src/test/kotlin/androidx/navigation/safe/args/generator/KotlinNavWriterTest.kt
A navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongNameMainFragmentArgs.kt
ap...@google.com <ap...@google.com> #11
Branch: snap-temp-L53700000699921463
commit 0e594474451b1e1a2f6927179c703f1547dbfe7c
Author: Jeremy Woods <jbwoods@google.com>
Date: Wed Sep 23 14:44:52 2020
Keep long arg names from wrapping
When using safe-args with a long argument name, kotlin does an
automatic line wrap at 100 characters which makes it place the argument
return and variable on different lines, which fails.
We should add `·` to emit a space that never wraps.
Test: Added KotlinNavWriterTest
Bug: 168584987
Change-Id: Ibc31f91fd6b1c4a94f0b7c05b9b9679ffa09b625
(cherry picked from commit 766a3ad47da73e1179ce6419693a825c4d665cfd)
M navigation/navigation-safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/kotlin/KotlinNavWriter.kt
M navigation/navigation-safe-args-generator/src/test/kotlin/androidx/navigation/safe/args/generator/KotlinNavWriterTest.kt
A navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongNameMainFragmentArgs.kt
ap...@google.com <ap...@google.com> #12
Branch: snap-temp-L25200000699921867
commit c9e5aa95317da82d339c362a3dae64a9f331e214
Author: Jeremy Woods <jbwoods@google.com>
Date: Wed Sep 23 14:44:52 2020
Keep long arg names from wrapping
When using safe-args with a long argument name, kotlin does an
automatic line wrap at 100 characters which makes it place the argument
return and variable on different lines, which fails.
We should add `·` to emit a space that never wraps.
Test: Added KotlinNavWriterTest
Bug: 168584987
Change-Id: Ibc31f91fd6b1c4a94f0b7c05b9b9679ffa09b625
(cherry picked from commit 766a3ad47da73e1179ce6419693a825c4d665cfd)
M navigation/navigation-safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/kotlin/KotlinNavWriter.kt
M navigation/navigation-safe-args-generator/src/test/kotlin/androidx/navigation/safe/args/generator/KotlinNavWriterTest.kt
A navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongNameMainFragmentArgs.kt
ap...@google.com <ap...@google.com> #13
Branch: snap-temp-L07700000699933785
commit f3069dc913eb8d41e6cc3859df5d6ef204fdf18c
Author: Jeremy Woods <jbwoods@google.com>
Date: Wed Sep 23 14:44:52 2020
Keep long arg names from wrapping
When using safe-args with a long argument name, kotlin does an
automatic line wrap at 100 characters which makes it place the argument
return and variable on different lines, which fails.
We should add `·` to emit a space that never wraps.
Test: Added KotlinNavWriterTest
Bug: 168584987
Change-Id: Ibc31f91fd6b1c4a94f0b7c05b9b9679ffa09b625
(cherry picked from commit 766a3ad47da73e1179ce6419693a825c4d665cfd)
M navigation/navigation-safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/kotlin/KotlinNavWriter.kt
M navigation/navigation-safe-args-generator/src/test/kotlin/androidx/navigation/safe/args/generator/KotlinNavWriterTest.kt
A navigation/navigation-safe-args-generator/src/test/test-data/expected/kotlin_nav_writer_test/ReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongNameMainFragmentArgs.kt
ma...@syslogic.io <ma...@syslogic.io> #14
Just gotten into a similar situation (the title matches): When calling method getArguments()
in the constructor, it returns null
.
While in public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
it returns the Bundle
Alike this I can obtain the arguments of a DialogFragment
inside a library, which doesn't know about the Navigation at all.
Description
STEPS:
DialogFragment
class calledMyDialog
MyNewDialog
that inherits fromMyDialog
instead ofDialogFragment
(for example, to inherit styles or UI)Now in a XML graph, try to add it and put it some
argument
s:Then, when trying to build the application, it won't compile and the navigation generated code will throw:
However, no problem with
actions
(Why it doesn't throw same error with typeMyNewDialogDirections
??)No problem if we use
MyDialog
(that inherits directly fromDialogFragment
) instead ofMyNewDialog
(that inherits fromMyDialog
and this last, fromDialogFragment