Search code examples
androidviewmodeldagger-2

How can I create ViewModel and inject repository to it with dagger 2?


I try understanding ViewModel. I create ViewModel:

public class UsersViewModel extends ViewModel {

    private final UsersRepository usersRepository;

    public UsersViewModel(UsersRepository usersRepository) {
        this.usersRepository = usersRepository;
    }

    public LiveData<List<User>> loadAll() {
        return usersRepository.getAll();
    }

}

But I not understand 2 things:

  1. How can I inject UsersRepository to this VievModel? When I used presenter I can create it with dagger 2 like this:
@Module
public class PresentersModule {

    @Singleton
    @Provides
    UsersPresenter provideUsersPresenter(UsersRepository usersRepository) {
        return new UsersPresenter(usersRepository);
    }
}

but how can I do it with ViewModel? Like this?

@Module
public class ViewModelsModule {

    @Singleton
    @Provides
    UsersViewModel provideUsersViewModel(UsersRepository usersRepository) {
        return new UsersViewModel(usersRepository);
    }
}
  1. How can I get this ViewModel in fragment? With presenter I can this:

    presenter = MyApplication.get().getAppComponent().getUsersPresenter();


Solution

  • ViewModel is created via ViewModelProvider that uses a ViewModelFactory to create the instances. You can't inject ViewModels directly, instead you should use a custom factory like below

    @Singleton
    public class DaggerViewModelFactory implements ViewModelProvider.Factory {
    private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
    
    @Inject
    public DaggerViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
        this.creators = creators;
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public <T extends ViewModel> T create(Class<T> modelClass) {
        Provider<? extends ViewModel> creator = creators.get(modelClass);
        if (creator == null) {
            for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
                if (modelClass.isAssignableFrom(entry.getKey())) {
                    creator = entry.getValue();
                    break;
                }
            }
        }
        if (creator == null) {
            throw new IllegalArgumentException("unknown model class " + modelClass);
        }
        try {
            return (T) creator.get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    }
    

    Then you need a module for the dagger that creates the view model factory and view models itself.

    @Module
    abstract class ViewModelModule {
    @Binds
    abstract ViewModelProvider.Factory bindViewModelFactory(DaggerViewModelFactory factory);
    
    @Binds
    @IntoMap
    @ViewModelKey(VideoListViewModel.class)
    abstract ViewModel provideVideoListViewModel(VideoListViewModel videoListViewModel);
    
    @Binds
    @IntoMap
    @ViewModelKey(PlayerViewModel.class)
    abstract ViewModel providePlayerViewModel(PlayerViewModel playerViewModel);
    
    @Binds
    @IntoMap
    @ViewModelKey(PlaylistViewModel.class)
    abstract ViewModel providePlaylistViewModel(PlaylistViewModel playlistViewModel);
    
    @Binds
    @IntoMap
    @ViewModelKey(PlaylistDetailViewModel.class)
    abstract ViewModel providePlaylistDetailViewModel(PlaylistDetailViewModel playlistDetailViewModel);
    }
    

    ViewModelKey file is like this

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

    Now to get your view model in the activity or fragment simply inject the view model factory and then use that factory to create the view model instances

    public class PlayerActivity extends BaseActivity {
         @Inject DaggerViewModelFactory viewModelFactory;
         PlayerViewModel viewModel;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            AndroidInjection.inject(this);
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_player);
            viewModel = ViewModelProviders.of(this,viewModelFactory).get(PlayerViewModel.class);
    
    }
    

    To inject anything to your ViewModel like the repository simply use Constructor Injection.

    public class PlayerViewModel extends ViewModel {
        private VideoRepository videoRepository;
        private AudioManager audioManager;
    
    
        @Inject
        public PlayerViewModel(VideoRepository videoRepository, AudioManager audioManager) {
             this.videoRepository = videoRepository;
             this.audioManager = audioManager;
    
        }
    }
    

    Check out the fully working example from here https://github.com/alzahm/VideoPlayer, Also I learned many of the dagger stuff from google samples, you can check them out as well.