Search code examples
androiddagger-2android-architecture-componentsandroid-architecture-lifecycle

How to inject dependencies in a SavedStateHandle-aware AndroidViewModel?


Assume a view model like this:

public class FooViewModel extends AndroidViewModel {

    @Inject public FooViewModel(Application app, SavedStateHandle handle, Bar bar) {
        // ...
    }
}

I want to inject Bar using Dagger 2. I am developing on Android.

According to the SavedStateHandle docs:

You should use SavedStateViewModelFactory if you want to receive this object in ViewModel's constructor.

However, the SavedStateViewModelFactory docs state that the factory is final which means I cannot inject Bar there, either.

So far, I have been injecting via a setter:

    @Provides
    FooViewModel provideFooViewModel(ViewModelStoreOwner owner, Bar bar) {
        FooViewModel viewModel = new ViewModelProvider(owner).get(FooViewModel.class);

        viewModel.setBar(bar);

        return viewModel;
    }

Is there a better way to do this?

I want to use constructor injection, mark the Bar instance variable as final and eliminate the setter.


Solution

  • To provide a FooViewModel, you need a custom implementation of AbstractSavedStateViewModelFactory.

    MyComponent component = DaggerMyComponent.withViewModelStoreOwner(this)
    .withSavedStateRegistryOwner(this)
    .withDefaultArguments(this.arguments != null ? this.arguments : new Bundle())
    .build();
    

    and

        @Provides
        @Suppress("UNCHECKED_CAST")
        public MyViewModel viewModel(ViewModelStoreOwner viewModelStoreOwner, SavedStateRegistryOwner savedStateRegistryOwner, Bundle defaultArgs, Application application, Bar bar) {
            return new ViewModelProvider(
                viewModelStoreOwner,
                new AbstractSavedStateViewModelFactory(savedStateRegistryOwner, defaultArgs) {
                    @Override
                    public <T extends ViewModel> T create(
                        String key,
                        Class<T> modelClass,
                        SavedStateHandle handle) {
                        return (T) new MyViewModel(application, handle, bar);
                    } 
                }).get(MyViewModel.class);
            });
        }
    

    Note:

    1.) you only get SavedStateHandle inside the AbstractSavedStateViewModelFactory, so you won't be able to get it into your graph.

    2.) you can reduce the length of that provider by using https://github.com/square/AssistedInject. Theoretically AutoFactory would also work, but it seems unmaintained in comparison.

    3.) you won't be able to get @Inject on your ViewModel.

    This answer was partly adapted from https://github.com/Zhuinden/DaggerViewModelExperiment/blob/c3cbf0a5bc85467cec08755fcc152db5e8c55f91/app/src/main/java/com/zhuinden/daggerviewmodelexperiment/features/second/SecondFragment.kt#L32-L47 .