Home » Android » android – ViewModelProviders with Dagger 2, not able to grasp the concept

android – ViewModelProviders with Dagger 2, not able to grasp the concept

Posted by: admin June 15, 2020 Leave a comment


I have a Retrofit Service like this

public interface BrandsService {
    Call<List<Brand>> getBrands();

Then I have a Repository to get data from an api like this

public class BrandsRepository {
    public static final String TAG = "BrandsRepository";
    MutableLiveData<List<Brand>> mutableLiveData;
    Retrofit retrofit;

    public BrandsRepository(Retrofit retrofit) {
        this.retrofit = retrofit;

    public LiveData<List<Brand>> getListOfBrands() {
//       Retrofit retrofit = ApiManager.getAdapter();
        final BrandsService brandsService = retrofit.create(BrandsService.class);
        Log.d(TAG, "getListOfBrands: 00000000000 "+retrofit);

        mutableLiveData = new MutableLiveData<>();

        Call<List<Brand>> retrofitCall = brandsService.getBrands();
        retrofitCall.enqueue(new Callback<List<Brand>>() {
            public void onResponse(Call<List<Brand>> call, Response<List<Brand>> response) {

            public void onFailure(Call<List<Brand>> call, Throwable t) {
        return mutableLiveData;


I am using constructor injection of Dagger2 by injecting Retrofit like that.
Then I have a ViewModel like this

public class BrandsViewModel extends ViewModel{
    BrandsRepository brandsRepository;
    LiveData<List<Brand>> brandsLiveData;
    public BrandsViewModel(BrandsRepository brandsRepository) {
        this.brandsRepository = brandsRepository;

    public void callService(){
       brandsLiveData = brandsRepository.getListOfBrands();

    public LiveData<List<Brand>> getBrandsLiveData() {
        return brandsLiveData;


To inject Retrofit in BrandsRepository, I have to inject BrandsRepository like that.
Then I have MainActivity like this

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    BrandsViewModel brandsViewModel;
    protected void onCreate(Bundle savedInstanceState) {


//        BrandsViewModel brandsViewModel = ViewModelProviders.of(this).get(BrandsViewModel.class);

        LiveData<List<Brand>> brandsLiveData = brandsViewModel.getBrandsLiveData();
        brandsLiveData.observe(this, new Observer<List<Brand>>() {
            public void onChanged(@Nullable List<Brand> brands) {
                Log.d(TAG, "onCreate: "+brands.get(0).getName());


The BrandsViewModel is injected using Dagger2 rather than ViewModelProviders. This is working fine but when I try to use ViewModelProviders by uncommenting it, dagger gives me the error which is obvious. The proper way of getting a ViewModel is by using ViewModelProviders but how will I accomplish this while injecting retrofit like that.

How to&Answers:

EDIT: An important note. To use Jetpack ViewModel, you don’t need map-multibinding. Read on.

The answer can be simpler than Mumi’s approach, which is that you expose the ViewModel on your component:

public interface SingletonComponent {
    BrandsViewModel brandsViewModel();

And now you can access this method on the component inside the ViewModelFactory:

// @Inject
BrandsViewModel brandsViewModel;

brandsViewModel = new ViewModelProvider(this, new ViewModelProvider.Factory() {
    public <T extends ViewModel> create(Class<T> modelClazz) {
        if(modelClazz == BrandsViewModel.class) {
            return singletonComponent.brandsViewModel();
        throw new IllegalArgumentException("Unexpected class: [" + modelClazz + "]");

All this can be simplified and hidden with Kotlin:

inline fun <reified T: ViewModel> AppCompatActivity.createViewModel(crossinline factory: () -> T): T = T::class.java.let { clazz ->
    ViewModelProvider(this, object: ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            if(modelClass == clazz) {
                return factory() as T
            throw IllegalArgumentException("Unexpected argument: $modelClass")

which now lets you do

brandsViewModel = createViewModel { singletonComponent.brandsViewModel() }

Where now BrandsViewModel can receive its parameters from Dagger:

class BrandsViewModel @Inject constructor(
    private val appContext: Context,
    /* other deps */
): ViewModel() {

Though the intent might be cleaner if a Provider<BrandsViewModel> is exposed from Dagger instead

interface SingletonComponent {
    fun brandsViewModel(): Provider<BrandsViewModel>

brandsViewModel = createViewModel { singletonComponent.brandsViewModel().get() }

With some additional trickery coming in from android-ktx, you could even do

inline fun <reified T : ViewModel> Fragment.fragmentViewModels(
    crossinline creator: () -> T
): Lazy<T> {
    return createViewModelLazy(T::class, storeProducer = {
    }, factoryProducer = {
        object : ViewModelProvider.Factory {
            override fun <T : ViewModel?> create(
                modelClass: Class<T>
            ): T = creator.invoke() as T

And then

class ProfileFragment: Fragment(R.layout.profile_fragment) {
    private val viewModel by fragmentViewModels {

Where brandsViewModelFactory() is

fun brandsViewModelFactory(): Provider<BrandsViewModel>


The answer is based on android-architecture-components.

You can use Map multibinding in Dagger.

First, to declare map key like this.

annotation class ViewModelKey(val value: KClass<out ViewModel>)

Second, to create map.

abstract class ViewModelModule {
    abstract fun bindUserViewModel(userViewModel: UserViewModel): ViewModel

    abstract fun bindSearchViewModel(searchViewModel: SearchViewModel): ViewModel

Next, to create the factory file to handle the map which uses key to choose ViewModel.

class GithubViewModelFactory @Inject constructor(
    private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        val creator = creators[modelClass] ?: creators.entries.firstOrNull {
        }?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
        try {
            return creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)

Finally, to inject factory in your activity or fragment.

class SearchFragment : Fragment(), Injectable {

    lateinit var viewModelFactory: ViewModelProvider.Factory

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        searchViewModel = ViewModelProviders.of(this, viewModelFactory)

By this way, you can inject repository in your ViewModel.

class SearchViewModel @Inject constructor(repoRepository: RepoRepository) : ViewModel() {