Dagger: Hilt - injecting ViewModel via navGraphViewModels issue

Created on 19 Oct 2020  路  4Comments  路  Source: google/dagger

Hello,
I had an issue with restoring state of injected viewmodel via navGraphViewModels.
I saw the recommended way for injectig viewmodels with the Jetpack navigation library (https://github.com/google/dagger/issues/1935), 2, however after process death ViewModel is not restored correctly on the second screen of navGraph.

I created demo project which demostrates the issue. We have a nested navigation graph R.id.nested which consists of 2 fragments. Those 2 fragments shares MainViewModel and MainViewModel2 which are completely similar and contains LiveData<String>.
The only difference is that first ViewModel initialized with fragment's defaultViewModelProviderFactory as recommended with Hilt, while second is initialized without passing custom factory, so uses backStackEntry's defaultViewModelProviderFactory (will not work when Hilt is used, but to demosntrate the problem).
```kotlin
private val viewModel by navGraphViewModels(R.id.nested) {
defaultViewModelProviderFactory
}

private val viewModel2 by navGraphViewModels<MainViewModel2>(R.id.nested)

```

First fragment in nested graph sets message to both viewmodels and then opens Second fragment.
With Don't keep activities enabled, putting app to background and back, triggers app process death and restore.
After process restore, MainViewModel doesn't have the value from First fragment and shows the default one, while MainViewModel2 is restored correctly and displays proper message.

  1. Enter message on FirstFragment and open SecondFragment.
    On SecondFragment both viewmodels have correct values.
  2. Background app with Don't keep activities to trigger process death and come back.
    On SecondFragment first viewmodel doesn't have restored value, second viewmodel has as expected.
  3. Press back to return on FirstFragment and open SecondFragment again
    On SecondFragment both viewmodels have correct values.
  4. Background app with Don't keep activities to trigger process death and come back.
    On SecondFragment both viewmodels contains correct value, even if you modify value on FirstFragment.
  5. Press back to return on FirstFragment, Background app with Don't keep activities and open SecondFragment.
    On SecondFragment both viewmodels have correct values.
  6. Background app with Don't keep activities to trigger process death and come back.
    On SecondFragment first viewmodel doesn't have restored value, second viewmodel has as expected.

demo

P2 hilt bug

All 4 comments

Thanks for the repro case and detailed write up. Just to clarify, you're running into this issue even when not using Hilt, right? The reason this is related to Hilt is that https://github.com/google/dagger/issues/1935 recommends passing in the defaultViewModelProviderFactory to navGraphViewModels and this causes the issue?

@Chang-Eric yes, this issue reproduces without Hilt when passing fragment's defaultViewModelProviderFactory to navGraphViewModels.
The reason this is related to Hilt is that with Hilt there is no way to use navGraphViewModels without passing fragment's defaultViewModelProviderFactory and this is recommened way in the docs. Without Hilt backStackEntry's defaultViewModelProviderFactory is used by default which doesn't have that issue.

The root cause of the issue is that the defaultViewModelProviderFactory from an activity or fragment uses the activity itself as the SavedStateRegistryOwner and that is different from the ViewModelStoreOwner used by the NavBackStack. It seems these two should be the same, which is exactly what is going on with the HiltViewModelFactory too, it uses the activity or fragment as SavedStateRegistryOwner which is different from the ViewModelStoreOwner that is used by the ViewModelProvider by theNavBackStack.

We are investigating a solution but sadly I don't have a workaround for now. We might either expose a builder / factory for HiltViewModelFactory (which is odd) or we might work with AndroidX itself to make it so that these two owners are in sync.

For now, in my sample, what I tried doing is:

And the ugliest part:

Not nice, but state restoration seems to work on my second screen now as well. It's unfortunate that I had not noticed earlier, considering that apparently the views restoring their own state has bamboozled me. :frowning:

Was this page helpful?
0 / 5 - 0 ratings

Related issues

SteinerOk picture SteinerOk  路  3Comments

makaroffandrey picture makaroffandrey  路  3Comments

mskx42 picture mskx42  路  3Comments

gc986 picture gc986  路  4Comments

feinstein picture feinstein  路  3Comments