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)
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() { ... }
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); } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With