Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android lifecycle library ViewModel using dagger 2

I have a ViewModel class just like the one defined in the Connecting ViewModel and repository section of Architecture guide. When I run my app I get a runtime exception. Does anyone know how to get around this? Should I not be injecting the ViewModel? Is there a way to tell the ViewModelProvider to use Dagger to create the model?

public class DispatchActivityModel extends ViewModel {      private final API api;      @Inject     public DispatchActivityModel(API api) {         this.api = api;     } } 

Caused by: java.lang.InstantiationException: java.lang.Class has no zero argument constructor at java.lang.Class.newInstance(Native Method) at android.arch.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:143) at android.arch.lifecycle.ViewModelProviders$DefaultFactory.create(ViewModelProviders.java:143)  at android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:128)  at android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:96)  at com.example.base.BaseActivity.onCreate(BaseActivity.java:65)  at com.example.dispatch.DispatchActivity.onCreate(DispatchActivity.java:53)  at android.app.Activity.performCreate(Activity.java:6682)  at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2619) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2727)  at android.app.ActivityThread.-wrap12(ActivityThread.java)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1478)  at android.os.Handler.dispatchMessage(Handler.java:102)  at android.os.Looper.loop(Looper.java:154)  at android.app.ActivityThread.main(ActivityThread.java:6121)

like image 308
TheHebrewHammer Avatar asked May 30 '17 19:05

TheHebrewHammer


2 Answers

Today I learnt a way to avoid having to write factories for my ViewModel classes:

class ViewModelFactory<T : ViewModel> @Inject constructor(     private val viewModel: Lazy<T> ) : ViewModelProvider.Factory {     @Suppress("UNCHECKED_CAST")     override fun <T : ViewModel?> create(modelClass: Class<T>): T = viewModel.get() as T } 

EDIT: As pointed out by @Calin in the comments, we are using Dagger's Lazy in the code snippet above, not Kotlin's.

Rather than injecting the ViewModel, you can inject a generic ViewModelFactory into your activities and fragments and obtain an instance of any ViewModel:

class MyActivity : Activity() {      @Inject     internal lateinit var viewModelFactory: ViewModelFactory<MyViewModel>     private lateinit var viewModel: MyViewModel      override fun onCreate(savedInstanceState: Bundle?) {         AndroidInjection.inject(this)         super.onCreate(savedInstanceState)         this.viewModel = ViewModelProviders.of(this, viewModelFactory)             .get(MyViewModel::class.java)         ...     }      ... } 

I used AndroidInjection.inject(this) as with the dagger-android library, but you can inject your activity or fragment the way you prefer. All that is left is to make sure you provide your ViewModel from a module:

@Module object MyModule {     @JvmStatic     @Provides     fun myViewModel(someDependency: SomeDependency) = MyViewModel(someDependency) }  

Or applying the @Inject annotation to its constructor:

class MyViewModel @Inject constructor(     someDependency: SomeDependency ) : ViewModel() {     ... } 
like image 27
argenkiwi Avatar answered Oct 01 '22 04:10

argenkiwi


You need to implement your own ViewModelProvider.Factory. There is an example app created by Google demonstrating how to connect Dagger 2 with ViewModels. LINK. You need those 5 things:

In ViewModel:

@Inject public UserViewModel(UserRepository userRepository, RepoRepository repoRepository) { 

Define annotation:

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

In ViewModelModule:

@Module abstract class ViewModelModule {     @Binds     @IntoMap     @ViewModelKey(UserViewModel.class)     abstract ViewModel bindUserViewModel(UserViewModel userViewModel); 

In Fragment:

@Inject ViewModelProvider.Factory viewModelFactory;  @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) {             userViewModel = ViewModelProviders.of(this, viewModelFactory).get(UserViewModel.class); 

Factory:

@Singleton public class GithubViewModelFactory implements ViewModelProvider.Factory {     private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;      @Inject     public GithubViewModelFactory(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);         }     } } 
like image 74
Robert Wysocki Avatar answered Oct 01 '22 04:10

Robert Wysocki