Dependency Injection of ViewModel with Dagger 2

By | 2019-09-06T18:38:10+00:00 December 26th, 2018|Android development|8 Comments

Official guidelines recommend using ViewModel architecture component in Android applications and Dagger 2 is the most popular dependency injection framework in Android world. One would expect these tools to integrate seamlessly, but, unfortunately, it’s not exactly the case.

In this article I’ll explain how to inject ViewModel instances with Dagger and show several alternative approaches that you can choose from. In addition, we are going to review one very serious mistake caused by violation of Liskov Substitution Principle that I myself made in this context.

ViewModel Without External Dependencies

Let’s say that you use ViewModel to just store some data on configuration changes. In this case, you won’t need to pass any arguments into its constructor.

For example, consider this ViewModel:

public class MyViewModel extends ViewModel {

    private List<MyData> mData = new ArrayList<>();

    public List<MyData> getData() {
        return mData;
    }

    public void setData(List<MyData> data) {
        mData = data;
    }
}

To use MyViewModel in your Activities and Fragments, all you need to do is the following:

     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mMyViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
     }

As you can see, if your ViewModel doesn’t have external dependencies, then you don’t need to integrate it with Dagger at all. That’s the simplest case.

Now, even though this scenario is simple, it’s still perfectly valid use case for ViewModel classes. If all you want to achieve is to keep some data on configuration changes, this approach will suffice and there is really no need to complicate things.

ViewModel With External Dependencies

Since the official guidelines recommend using ViewModels to host the actual presentation layer logic, not just some data, the previous approach won’t be sufficient in many cases. In the most general scenario, to get a clean code that adheres to Single Responsibility Principle, you’ll need to inject additional external dependencies into your ViewModels.

For example, consider the following ViewModel. It uses FetchDataUseCase to fetch some data and then publishes the result using LiveData:

public class MyViewModel extends ViewModel {

    private final FetchDataUseCase mFetchDataUseCase;

    private final MutableLiveData<List<MyData>> mDataLive = new MutableLiveData<>();
    
    private final FetchDataUseCase.Listener mUseCaseListener = new FetchDataUseCase.Listener() {
        @Override
        public void onFetchDataSucceeded(List<MyData> data) {
            mDataLive.postValue(data);
        }

        @Override
        public void onFetchDataFailed() {
            mDataLive.postValue(null);
        }
    };

    @Inject
    public MyViewModel(FetchDataUseCase fetchDataUseCase) {
        mFetchDataUseCase = fetchDataUseCase;
        mFetchDataUseCase.registerListener(mUseCaseListener);
    }

    public LiveData<List<MyData>> getDataLive() {
        return mDataLive;
    }

    public void fetchData() {
        mFetchDataUseCase.fetchData();
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        mFetchDataUseCase.unregisterListener(mUseCaseListener);
    }
}

To instantiate this ViewModel, the system needs to pass FetchDataUseCase object into its constructor. If you attempt to use the previously described approach in this case, you’ll get a runtime error. Therefore, we need a more sophisticated technique.

Enter ViewModelProvider.Factory interface:

    /**
     * Implementations of {@code Factory} interface are responsible to instantiate ViewModels.
     */
    public interface Factory {
        /**
         * Creates a new instance of the given {@code Class}.
         * <p>
         *
         * @param modelClass a {@code Class} whose instance is requested
         * @param <T>        The type parameter for the ViewModel.
         * @return a newly created ViewModel
         */
        @NonNull
        <T extends ViewModel> T create(@NonNull Class<T> modelClass);
    }

This interface realizes parametrized abstract factory design pattern. Its create(Class) method receives a class object that represents the required ViewModel type as an argument and returns a new instance of the corresponding class.

To initialize ViewModels with non-default constructors, you’ll need to inject an object that implements ViewModelProvider.Factory interface into your Activities and Fragments, and then pass it to ViewModelProviders.of() method:


     @Inject ViewModelFactory mViewModelFactory;

     private MyViewModel mMyViewModel;

     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mMyViewModel = ViewModelProviders.of(this, mViewModelFactory).get(MyViewModel.class);
     }

So far it doesn’t look too complex, but I haven’t shown you the implementation of ViewModelFactory yet. That’s where things get more involved.

ViewModelFactory Returns Pre-Constructed ViewModels (BAD!)

You can implement ViewModelFactory in the following way:

public class ViewModelFactory implements ViewModelProvider.Factory {

    private final MyViewModel mMyViewModel;

    @Inject
    public ViewModelFactory(MyViewModel myViewModel) {
        mMyViewModel = myViewModel;
    }

    @SuppressWarnings("unchecked")
    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        ViewModel viewModel;
        if (modelClass == MyViewModel.class) {
            viewModel = mMyViewModel;
        }
        else {
            throw new RuntimeException("unsupported view model class: " + modelClass);
        }

        return (T) viewModel;
    }
}

This factory receives pre-constructed ViewModels as constructor arguments. Unfortunately, if you’ll have 20 ViewModels in your application, then you’ll need to pass 20 arguments into ViewModelFactory’s constructor. It’s quite ugly.

However, boilerplate isn’t the biggest problem with this implementation.

Note that to get an instance of such ViewModelFactory, all ViewModels that it provides will need to be instantiated as well. So, if you’ll have 50 ViewModels with 100 transitive dependencies and you’ll want to get an instance of ViewModelFactory, you’ll need to construct 150+ objects. Every single time. Not good.

But even that isn’t the biggest problem. The main problem with this implementation that makes it absolute no-go is the fact that it violates Liskov Substitution Principle and can lead to serious bugs in your application.

Consider this part of the contract of create(Class) method in ViewModelProvider.Factory interface:

Creates a new instance of the given {@code Class}.

Can you see the problem?

The above implementation of ViewModelFactory doesn’t create a new instance. It will always return the same instance given the same argument. That’s really bad.

For example, imagine that one day you’ll want to use the same ViewModel class in Activity and Fragment. Furthermore, you’ll want to get Activity’s ViewModel from within the Fragment. Something like this:

     @Inject ViewModelFactory mViewModelFactory;

     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mMyViewModel = ViewModelProviders.of(this, mViewModelFactory).get(MyViewModel.class);
         mMyViewModelActivity = ViewModelProviders.of(requireActivity(), mViewModelFactory).get(MyViewModel.class);
     }

Naturally, you’d expect these ViewModels to be different objects. However, given this implementation of ViewModelFactory, it’s not guaranteed and the result will depend on whether Activity’s ViewModel had been accessed in the past in another place in the application. That’s a serious bug that can be extremely difficult to find and fix due to dependency on the previous history.

Therefore, you should never use this approach.

If you find it difficult to see why this implementation of ViewModelFactory is problematic, then it’s worth taking time to understand this point. I myself made a serious error initially and had’t realized the problem for months, so it’s alright if you find it tricky. You can read more about Liskov Substitution Principle in this article.

ViewModelFactory Obtains ViewModels From Dagger’s Providers

The above implementation shouldn’t be used because it can lead to unintended reuse of ViewModel instances. Luckily, there is relatively simple way to address this issue using Dagger’s Providers.

This implementation of ViewModelFactory is safe:

public class ViewModelFactory implements ViewModelProvider.Factory {

    private final Provider<MyViewModel> mMyViewModelProvider;

    @Inject
    public ViewModelFactory(Provider<MyViewModel> myViewModelProvider) {
        mMyViewModelProvider= myViewModelProvider;
    }

    @SuppressWarnings("unchecked")
    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        ViewModel viewModel;
        if (modelClass == MyViewModel.class) {
            viewModel = mMyViewModelProvider.get();
        }
        else {
            throw new RuntimeException("unsupported view model class: " + modelClass);
        }

        return (T) viewModel;
    }
}

Now, instead of injecting ViewModels themselves into ViewModelFactory, I inject Providers of the respective ViewModels.

Providers are like factories for a single type. However, unlike factories, the instances provided by Providers aren’t necessarily distinct. For example, if MyViewModel in the above example would be defined as @Singleton scoped in application, then each call to mMyViewModelProvider.get() would return the same instance. In other words, Providers are Dagger’s delegates that you can use inside your own classes.

The cool thing about Providers is that you don’t need to add them to objects graph manually. Dagger is smart enough to generate Provider<SomeClass> for you if you have SomeClass on the objects graph. Therefore, this change is conveniently limited to just ViewModelFactory.

Dagger’s Multi-Binding

To address the issue of boilerplate in the above implemenation, you can use special Dagger’s convention called “multi-bindings”. It’s relatively complex, so I’ll describe it in steps.

You’ll start by defining a special annotation (I do it in the module class, but it’s not mandatory):

@Module
public class ViewModelModule {

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @MapKey
    @interface ViewModelKey {
        Class<? extends ViewModel> value();
    }

}

ViewModelKey annotation, when used on provider methods, basically says that the services returned by these methods must be put into special Map. The keys in this Map will be of type Class<? extends ViewModel> and the values will be of type <? extends ViewModel> (subclass of ViewModel).

I’m pretty sure that this explanation sounds absolutely gibberish at this point. Hopefully, it will become clearer after you see the entire picture.

Once I have ViewModelKey annotation, I can add provider method for a specific ViewModel in the following manner:

@Module
public class ViewModelModule {

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @MapKey
    @interface ViewModelKey {
        Class<? extends ViewModel> value();
    }

    @Provides
    @IntoMap
    @ViewModelKey(MyViewModel1.class)
    ViewModel viewModel1(FetchDataUseCase1 fetchDataUseCase1) {
        return new MyViewModel1(fetchDataUseCase1);
    }
}

Note that the return type of the provider method is ViewModel and not ViewModel1. It’s intentional. @IntoMap annotation says that Provider object for this service will be inserted into Map, and @ViewModelKey annotation specifies under which key it will reside. Still gibberish, I know.

The net result of having the above code is that Dagger will create implicit Map data structure filled with Provider<ViewModel> objects and put it onto the objects graph. You can make use of that Map by passing it into ViewModelFactory in the following manner:

@Module
public class ViewModelModule {

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @MapKey
    @interface ViewModelKey {
        Class<? extends ViewModel> value();
    }

    @Provides
    ViewModelFactory viewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> providerMap) {
        return new ViewModelFactory(providerMap);
    }

    @Provides
    @IntoMap
    @ViewModelKey(MyViewModel1.class)
    ViewModel viewModel1(FetchDataUseCase1 fetchDataUseCase1) {
        return new MyViewModel1(fetchDataUseCase1);
    }
}

So, in essence, all this magic was required to allow Dagger to create this Map that you pass into ViewModelFactory. The implementation of the factory then becomes:

public class ViewModelFactory implements ViewModelProvider.Factory {

    private final Map<Class<? extends ViewModel>, Provider<ViewModel>> mProviderMap;

    @Inject
    public ViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> providerMap) {
        mProviderMap = providerMap;
    }

    @SuppressWarnings("unchecked")
    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        return (T) mProviderMap.get(modelClass).get();
    }
}

As you can see, the factory becomes relatively simple. It retrieves the required Provider object from the Map, calls its get() method, casts the obtained reference to the required type and returns it. The nice thing about it is that this code is independent of the actual types of ViewModels in your application, so you don’t need to change anything here when you add new ViewModel classes.

Now, to add new ViewModel, you’ll simply add the respective provider method:

@Module
public class ViewModelModule {

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @MapKey
    @interface ViewModelKey {
        Class<? extends ViewModel> value();
    }

    @Provides
    ViewModelFactory viewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> providerMap) {
        return new ViewModelFactory(providerMap);
    }

    @Provides
    @IntoMap
    @ViewModelKey(MyViewModel1.class)
    ViewModel viewModel1(FetchDataUseCase1 fetchDataUseCase1) {
        return new MyViewModel1(fetchDataUseCase1);
    }

    @Provides
    @IntoMap
    @ViewModelKey(MyViewModel2.class)
    ViewModel viewModel2(FetchDataUseCase2 fetchDataUseCase2) {
        return new MyViewModel2(fetchDataUseCase2);
    }
}

Dagger will automatically put Provider for ViewModel2 into the Map and ViewModelFactory will be able to use it.

The benefit of this method is clear: it reduces the amount of boilerplate that you need to write. However, there is also one major drawback: it increases the complexity of the code. Even experienced developers find this multi-bindings stuff tricky to understand and debug. For less experienced ones, it might be absolute hell.

Therefore, I wouldn’t recommend using this approach on projects with many developers, or if staff turnover rate is high. On such projects, the effort of learning Dagger’s multi-bindings might very well be higher than its gains. On small or personal projects, on the other hand, this approach might be alright.

Kotlin Magic

Gabor Varadi (aka Zhuinden, EpicPandaForce) described another original approach to ViewModel’s injection using Dagger in this StackOverflow answer. This technique would be tedious in Java because you’d need to create anonymous ViewModelFactory for each ViewModel, but Kotlin has some sophisticated tricks that allow you to automate this process.

I can’t recommend this approach as it looks quite complex and introduces some additional complexity, but I might be wrong. In any case, it’s really clever piece of code and I decided to include a link to it for completeness.

Why ViewModel Needs Special Approach to be Used With Dagger

One very interesting question to ask in context of ViewModel is: why it requires a special approach which is even more complicated than the “standard Dagger” (which isn’t simple to begin with)?

Well, consider this code that you’d use in either Activity or Fragment:


     @Inject ViewModelFactory mViewModelFactory;

     private MyViewModel mMyViewModel;

     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mMyViewModel = ViewModelProviders.of(this, mViewModelFactory).get(MyViewModel.class);
     }

Note that what you need in this Activity or Fragment is MyViewModel, but what’s being injected is ViewModelFactory.

Factories are very useful constructs when you need to instantiate objects with runtime dependencies, or when you need to instantiate many objects at runtime, or when the specific type of object that you’ll need isn’t known at compile time. Nothing of that applies in this case, however. You need one instance of ViewModel and you know its type and its dependencies at compile time. Therefore, usage of abstract factory pattern is unjustified in this case.

Unnecessary dependencies like ViewModelFactory violate fundamental principle of object-oriented design called Law of Demeter. Usually you can refactor the code to fix LoD violations, but, in this case, it’s violated by ViewModel framework itself, so you can’t work around that. It’s the violation of Law of Demeter that causes the troubles with respect to dependency injection. Just think about it for a moment: you already have your ViewModels on the object graph, but, instead of simply injecting them directly, you are forced to use ViewModelFactory. It’s a shame.

The reason I bring this topic up is to show you that it’s important to know the rules of object-oriented design and apply them in practice. Especially if you design APIs for millions of other developers to use.

Conclusion

In this post I demonstrated several alternative approaches to integrate ViewModel with Dagger. I remind you that one of them violates Liskov Substitution Principle, and, as such, should never be used.

When it comes to ViewModel, there is no “clean” way of doing things. Therefore, you need to take into account your specific constraints and make an educated trade-off. In essence, you need to choose the lesser of the evils.

To make it absolutely clear for long-term readers of my blog: the fact that I write about ViewModel doesn’t mean that I encourage you to use it. My opinion about this framework hasn’t improved. If anything, it became even more negative. I don’t use ViewModel in my projects, and I don’t recommend you to use it either. I wrote this post because many developers search for this information.

As usual, you can leave your comments and questions below.

Check out my advanced Android development courses on Udemy

Subscribe for new posts!

8 Comments

  1. George K January 6, 2019 at 12:01 pm - Reply

    All of this presents itself to me as a strong argument to throw Java in the trash can, forget that you ever had to deal with Dagger’s nonsense, and move to Koin with Kotlin. It’s really ridiculous.

    • Vasiliy January 6, 2019 at 12:11 pm - Reply

      Hello George,
      As I explained in the article, IMHO ViewModel’s API was poorly designed, which gives rise to all these complications. No language or framework will “hide” that fact.
      If anything, Dagger’s multi-bindings opens some options which means that developers can choose their poison at least.
      Therefore, I don’t think that either Kotlin or Koin have any kind of simplification to offer. Or, maybe, I’m missing something?

  2. Iury March 5, 2019 at 3:06 am - Reply

    Congratulations Vasiliy. Very good explanation. It has worked. I’d like to share the approach below which avoids boilerplate code with Dagger 2. I joined your view model injection approach with the one below.

    https://proandroiddev.com/using-dagger-android-in-a-multi-module-project-e751afaa53ba

  3. Matheus Queiroz March 9, 2019 at 12:31 pm - Reply

    Unfortunately, the real problem is still present even with these improvements.

    The code has a pretty serious ambiguity: you can scope your factory and dependencies using dagger scopes, but in the end, the factory create method will only be called when there is no instance of VM cached in the ViewModelStore. If there is an instanced cached, it will just return it, containing references to dependencies that differ from the ones in the component created at onCreate.

    Any thought on that?

    • Vasiliy April 15, 2019 at 9:23 am - Reply

      Hello Matheus,
      I don’t see a problem with that, as long as ViewModel doesn’t have a direct or transitive reference to Activity. However, this is a general limitation of ViewModel framework which can’t be worked around. It’s one of the reasons I don’t use ViewModel myself.
      Can you think of any specific scenario where what you described might become an issue?

  4. Ashutosh Gupta March 30, 2019 at 6:58 am - Reply

    Thanks Vasiliy for this amazing step by step explanation. However, don’t you think if we add multiple view models in below module, It’ll initialise all view models at once to create map for Factory ?

    [Vasiliy: I removed the code because it was identical to the example with multi-bindings and was a bit too long for a comment]

    • Vasiliy April 15, 2019 at 9:20 am - Reply

      Hello Ashutosh,
      I edited your question a bit and removed the code. You basically ask whether in multi-binding example all ViewModels will be instantiated right away to create the Map, right?
      Note that the map is of type Map, Provider> providerMap>. It doesn’t contain ViewModels, but Provider objects. Instantiating Providers is cheap, therefore it’s not a problem. ViewModel themselves will be instantiated only when you call get() method of the respective Provider, in other words – each ViewModels will be instantiated only when it’s needed.
      Cheers

  5. Hoa Nguyen September 3, 2019 at 3:22 am - Reply

    Nice explanation , dagger 2 really powerfull, thanks you so much !!

Leave A Comment