I'm using a navigation-component in my application and also using shared ViewModel between multiple fragments that are in the same graph. Now I want to instantiate the ViewModel with this graph scope with this.
As you know, in fragments we should inject objects ( ViewModel,..etc ) in onAttach
:
but when I want to do this (injecting ViewModel with a graph scope in onAttach
), this error occurs:
IllegalStateException: NavController is not available before onCreate()
Do you know how I can do this?
In short, you could provide the ViewModel
lazily with dagger Provider
or Lazy
.
The long explanation is:
Your injections points are correct. According to https://dagger.dev/android#when-to-inject
DaggerActivity calls AndroidInjection.inject() immediately in onCreate(), before calling super.onCreate(), and DaggerFragment does the same in onAttach().
The problem is some kind of race condition between when Android recreates the Activity
and the Fragments
attached to the FragmentManger
and when the NavController
can be provided. More specifically:
Activity
that has Fragments
attached is destroyed by the OS (can be reproduced with "don't keep Activities" from "developer settings")Activity
, OS proceeds to recreate the Activity
Activity
calls setContentView
while being recreated.Fragments
in the FragmentManager
to be reattached, which involve calling Fragment#onAttach
Fragment
is injected in Fragment#onAttach
NavController
BUT you cannot get the NavController
from the Activity
by this point, as Activity#onCreate
has not finished yet and you get
IllegalStateException: NavController is not available before onCreate()
The solution I found is to inject provide the NavCotroller
or things that depend on the NavController
(such as the ViewModel
, because Android needs the NavController
to get nav-scoped VideModels
) lazily. This can be done in two ways:
Lazy
Provided
(REF: https://proandroiddev.com/dagger-2-part-three-new-possibilities-3daff12f7ebf)
ie: inject the ViewModel
to the Fragment
or implementation of navigator like this:
@Inject
lateinit var viewModel: Provider<ViewModel>
then use it like this:
viewModel.get().events.observe(this) {....}
Now, the ViewModel
can by provided by Dagger like:
@Provides
fun provideViewModel(
fragment: Fragment,
argumentId: Int
): CreateMyViewModel {
val viewModel: CreateMyViewModel
by fragment.navGraphViewModels(R.id.nested_graph_id)
return viewModel
}
Dagger won't try to resolve the provisioning when the Fragment
is injected, but when it's used, hence, the race condition will be solved.
I really hate not being able to use my viewModels directly and need to use Provider
, but it's the only workaround I see to solve this issue, which I'm sure it was an oversight by Google (I don't blame them, as keeping track of the absurd lifecycle of Fragment and Activities is so difficult).
...we should inject objects ( ViewModel,..etc ) in onAttach...
Looks like it is currently a no go for such injection with the original by navGraphViewModels(R.id.nav_graph)
delegated property provided by androidx.navigation
package because from the source code
findNavController().getBackStackEntry(navGraphId)
and
public final NavController getNavController()
it stated that:
* Returns the {@link NavController navigation controller} for this navigation host. * This method will return null until this host fragment's {@link #onCreate(Bundle)}
And here are some workarounds:
https://github.com/InsertKoinIO/koin/issues/442
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With