问题
I am following this documentation to learn about LiveData and ViewModel. In the doc, the ViewModel class has constructor as such,
public class UserModel extends ViewModel {
private MutableLiveData<User> user;
@Inject UserModel(MutableLiveData<User> user) {
this.user = user;
}
public void init() {
if (this.user != null) {
return;
}
this.user = new MutableLiveData<>();
}
public MutableLiveData<User> getUser() {
return user;
}
}
However, when I run the code, I get exception:
final UserViewModelviewModel = ViewModelProviders.of(this).get(UserViewModel.class);
Caused by: java.lang.RuntimeException: Cannot create an instance of class UserViewModel Caused by: java.lang.InstantiationException: java.lang.Class has no zero argument constructor
回答1:
While initializing subclasses of ViewModel
using ViewModelProviders
, by default it expects your UserModel
class to have zero argument constructor.
In your case your constructor has argument MutableLiveData<User> user
One way to fix this is to have a default no arg constructor for your UserModel
Otherwise, if you want to have a non zero argument constructor for your ViewModel class, you may have to create a custom ViewModelFactory
class to initialise your ViewModel instance, which will implement ViewModelProvider.Factory
interface.
I have not tried this yet, but here is the link to excellent sample from google for the same: github.com/googlesamples/android-architecture-components. Specifically, checkout this class GithubViewModelFactory.java for Java code and this class GithubViewModelFactory.kt for corresponding Kotlin code
回答2:
ViewModelFactory
that will provide us a right ViewModel from ViewModelModule
public class ViewModelFactory implements ViewModelProvider.Factory {
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModels;
@Inject
public ViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModels) {
this.viewModels = viewModels;
}
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
Provider<ViewModel> viewModelProvider = viewModels.get(modelClass);
if (viewModelProvider == null) {
throw new IllegalArgumentException("model class " + modelClass + " not found");
}
return (T) viewModelProvider.get();
}
}
ViewModelModule
is responsible for binding all over ViewModel classes into Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModels
@Module
public abstract class ViewModelModule {
@Binds
abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory viewModelFactory);
//You are able to declare ViewModelProvider.Factory dependency in another module. For example in ApplicationModule.
@Binds
@IntoMap
@ViewModelKey(UserViewModel.class)
abstract ViewModel userViewModel(UserViewModel userViewModel);
//Others ViewModels
}
ViewModelKey
is an annotation for using as a key in the Map and looks like
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@MapKey
@interface ViewModelKey {
Class<? extends ViewModel> value();
}
Now you are able to create ViewModel and satisfy all necessary dependencies from the graph
public class UserViewModel extends ViewModel {
private UserFacade userFacade;
@Inject
public UserViewModel(UserFacade userFacade) { // UserFacade should be defined in one of dagger modules
this.userFacade = userFacade;
}
}
Instantiating ViewModel
public class MainActivity extends AppCompatActivity {
@Inject
ViewModelFactory viewModelFactory;
UserViewModel userViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((App) getApplication()).getAppComponent().inject(this);
userViewModel = ViewModelProviders.of(this, viewModelFactory).get(UserViewModel.class);
}
}
And do not forger to add ViewModelModule
into modules
list
@Singleton
@Component(modules = {ApplicationModule.class, ViewModelModule.class})
public interface ApplicationComponent {
//
}
回答3:
I wrote a library that should make achieving this more straightforward and way cleaner, no multibindings or factory boilerplate needed, while also giving the ability to further parametrise the ViewModel
at runtime:
https://github.com/radutopor/ViewModelFactory
@ViewModelFactory
class UserViewModel(@Provided repository: Repository, userId: Int) : ViewModel() {
val greeting = MutableLiveData<String>()
init {
val user = repository.getUser(userId)
greeting.value = "Hello, $user.name"
}
}
In the view:
class UserActivity : AppCompatActivity() {
@Inject
lateinit var userViewModelFactory2: UserViewModelFactory2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
appComponent.inject(this)
val userId = intent.getIntExtra("USER_ID", -1)
val viewModel = ViewModelProviders.of(this, userViewModelFactory2.create(userId))
.get(UserViewModel::class.java)
viewModel.greeting.observe(this, Observer { greetingText ->
greetingTextView.text = greetingText
})
}
}
回答4:
The problem can be resolved by extending UserModel
from AndroidViewModel
which is application context aware ViewModel and requires Application
parameter-only constructor. (documentation)
Ex- (in kotlin)
class MyVm(application: Application) : AndroidViewModel(application)
This works for version 2.0.0-alpha1
.
回答5:
If you have parameter in constructor then :
DAGGER 2 public constructor for @inject dependency
@Inject
public UserViewModel(UserFacade userFacade)
{
this.userFacade = userFacade;
}
Otherwise dagger 2 will send you error "can not instantiate viewmodel object"
来源:https://stackoverflow.com/questions/44194579/android-viewmodel-has-no-zero-argument-constructor