Status Update
Comments
il...@google.com <il...@google.com> #2
Assuming you're calling both these on the main thread, there cannot be a race condition (since the construction of the ViewModel happens synchronously). I look forward to your reproduction project.
Note that:
-
SavedStateViewModelFactory
is the default on Navigation 2.2.0+, so you can leave out the factory entirely. -
You don't need to use
ViewModelProvider
at all when in Kotlin. Instead, you'd want to useval viewModel: WorksViewModel by viewModels({ findNavController().previousBackStackEntry!! })
and similarly for yourcurrentBackStackEntry
.
[Deleted User] <[Deleted User]> #3
Ok, I managed to reproduce it:
- When you launch the app, "Fragment A" starts and inside onStart prints:
I/System.out: FragmentA gets instance: co.bstorm.navtest.FKCZViewModel@be69e6b
- Click on the "SETTINGS" button on the top right, "DialogFragment B" launches and inside onStart prints:
I/System.out: DialogFragmentB gets instance: co.bstorm.navtest.FKCZViewModel@be69e6b
This is normal as be69e6b == be69e6b, they both get the same instance.
- Now, while you're on DialogFragmentB, trigger either a process kill or activity kill (via developer options), and then go back to the app. Both Fragments get instantiated, their respective onStart methods run and this gets printed:
I/System.out: FragmentA gets instance: co.bstorm.navtest.FKCZViewModel@7fbae4b
I/System.out: DialogFragmentB gets instance: co.bstorm.navtest.FKCZViewModel@d219bc3
This time they get 2 different instances of the same ViewModel as 7fbae4b != d219bc3.
As you suggested, Fragment A uses:
val vm: FKCZViewModel by viewModels({ findNavController().currentBackStackEntry!! })
while DialogFragment B uses:
val vm: FKCZViewModel by viewModels({ findNavController().previousBackStackEntry!! })
Hope this helps :)
il...@google.com <il...@google.com> #4
I added some additional logging and that pointed out the problem pretty quickly:
val vm: FKCZViewModel by viewModels({
val backStackEntry = findNavController().currentBackStackEntry!!
println("FragmentA gets back stack entry: $backStackEntry, ${backStackEntry.destination}")
backStackEntry
})
Gives a logcat after process death of:
FragmentA gets back stack entry: androidx.navigation.NavBackStackEntry@af1aa01, Destination(co.bstorm.navtest:id/newsDetailFragment) label=label
Which is technically correct: the currentBackStackEntry
is indeed your dialog NewsDetailFragment
when your DialogFragment is above it and not the same NavBackStack
entry associated with your FragmentA.
Using getBackStackEntry(R.id.fkczFragment)
(which will always point to the same instance) rather than the relative currentBackStackEntry
ensures that you always get the right NavBackStackEntry
related to your FragmentA.
Given what I'm seeing, I don't think there's anything that is wrong, but there is a lot of unexpected behavior here when it comes to DialogFragments (so much so that we already had a
At the very least, we should update the docs to call this out, but I'll take a look to see if there is any additional way we can help developers avoid these kind of pitfalls.
[Deleted User] <[Deleted User]> #5
I see.. yeah now it works fine. This is fine by me, as far as I'm concerned there's nothing else you should do, it's just that this should be properly documented when it gets to beta or stable release.
Thanks!
[Deleted User] <[Deleted User]> #6
What I would like to know is, will this only happen for dialogFragments? If it was a normal fragment would it work correctly? As far as I see it would be the same as the backstack would be restored to what was before, and currentBackStackEntry would still be not what we expect, perhaps I'm wrong.
il...@google.com <il...@google.com> #7
Re #6 - if you're only accessing the ViewModel in onCreateView()
/onViewCreated()
(which would be the correct place to observe()
for results), those won't be called when you have another <fragment>
destination on top of you - they'll only be called once your fragment destination becomes the current destination, so that case will work as expected.
Description
Component used: Navigation Version used: 2.3.0-alpha05
I have a simple case:
Fragment A -> DialogFragment B (and B returns the "result" to A through ViewModel fetched via "previousBackStackEntry" ViewModelStore.
Fragment A instantiates ViewModel like this:
DialogFragment B instantiates ViewModel like this:
Normally everything works fine, and both Fragments receive the same instance of ViewModel, and communicate properly.. until I trigger Process kill or Activity kill while on DialogFragment B.
In that case, upon returning to application, since both Fragments create almost at the same time, because B is a Dialog and visible above A, I see that now 2 separate instances of the same ViewModel class are created, every fragment receives its' own, and communication between Fragments is broken.
I will try to make and attach a reproduction project on your request, but I'm starting only with description of the issue in case I'm doing something obviously wrong, but I doubt it since everything works perfectly fine until Activity kill happens while on DialogFragment B. (if I kill it while on A, still everything works fine upon returning). So there's obviously some race condition happening due to both Fragment A and DialogFragment B above it instantiating at almost the same time and trying to fetch a ViewModel instance at almost the same time..