I'm using Dagger to inject a viewModel into a fragment:
class BaseFragment<T extends BaseViewModel> extends Fragment {
@Inject T viewModel;
@Override
public void onAttach(Context context) {
super.onAttach(context);
if(viewModel == null) {
throw new RuntimeException("Viewmodel was null: "+getClass());
}
viewModel.setContext(context);
viewModel.onAttach(context);
}
}
class MyFragment extends BaseFragment<MyViewModel> {
public MyFragment() {
MyApp.getInstance().getComponent().inject(this);
//viewModel should be available at this point, before OnAttach is called
}
}
So in short I inject the viewModel in the constructor and if its still null by onAttach something was wrong.
And this never happens except maybe 1 out of a 100000 times when it does. Just a couple of crashes. But can't figure out why. Is this approach wrong? Does Dagger have a problem with the parameterized object somehow?
I don't instantiate BaseFragment directly so the type should work and it usually does so then why doesn't it work in some cases?
Injecting in the constructor of a Fragment is incorrect:
public MyFragment() {
//MyApp.getInstance().getComponent().inject(this);
//don't inject in a constructor!
}
While this may work for a very simple workflow, it won't correctly handle the Fragment lifecycle. In particular, there is a case where a Fragment exists but is detached from an Activity. When this happens and it is necessary for the Fragment to be shown to the again user, the Android OS will attempt to reattach the cached Fragment without calling the constructor (since an instance is already present). Since you are relying on the assumption that the constructor is always freshly called before onAttach
it is conceivable that this situation is causing your crash.
While it may be difficult to replicate this issue yourself with normal interaction with your app, I suspect you would be more likely to encounter it if you test your app with System/DeveloperOptions/Don't keep activities
switched on.
The correct way of injecting a subclass of Fragment is in onAttach(Context context)
:
@Override
public void onAttach(Context context) {
MyApp.getInstance().getComponent().inject(this);
super.onAttach(context); //call super.onAttach
}
This will more correctly track the Fragment lifecycle.
Note the request for injection before the super
call. This is as per the advice in the Dagger official documentation.