2

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.

Eureton
  • 162
  • 1
  • 7
  • To answer this question, I need to know how you managed to get the ViewModelStoreOwner into the graph. – EpicPandaForce Apr 17 '20 at 17:27
  • @EpicPandaForce I used a `@Subcomponent.Builder` with a `@BindsInstance Builder withViewModelStoreOwner(ViewModelStoreOwner owner);` method. This I call in my fragment with `withViewModelStoreOwner(this)`. Does this answer your question? – Eureton Apr 17 '20 at 17:31

1 Answers1

2

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 .

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428