livedata - Android ViewModel no tiene constructor argumento cero
viewmodel android (5)
Al inicializar las subclases de ViewModel
usando ViewModelProviders
, por defecto, espera que su clase UserModel
tenga cero constructor de argumentos. En su caso, su constructor tiene argumento MutableLiveData<User> user
Una forma de solucionar este problema es tener un constructor de UserModel
no predeterminado para su UserModel
De lo contrario, si desea tener un constructor de argumento distinto de cero para su clase ViewModel, es posible que deba crear una clase personalizada de ViewModelFactory
para inicializar su instancia de ViewModel, que implementará la interfaz ViewModelProvider.Factory
.
No he intentado esto todavía, pero aquí está el enlace a una excelente muestra de google para el mismo: github.com/googlesamples/android-architecture-components . Específicamente, GithubViewModelFactory.java esta clase GithubViewModelFactory.java para el código Java y esta clase GithubViewModelFactory.kt para el código Kotlin correspondiente
Estoy siguiendo this documentación para aprender sobre LiveData y ViewModel. En el documento, la clase ViewModel tiene un constructor como tal,
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;
}
}
Sin embargo, cuando ejecuto el código, obtengo una excepción:
final UserViewModelviewModel = ViewModelProviders.of(this).get(UserViewModel.class);
Causado por: java.lang.RuntimeException: No se puede crear una instancia de la clase UserViewModel Causado por: java.lang.InstantiationException: java.lang.Class no tiene un constructor de argumento cero
El problema se puede resolver extendiendo UserModel
desde AndroidViewModel
que es compatible con el contexto de la aplicación ViewModel y requiere un constructor de parámetros de Application
solamente. (documentation)
Ex- (en kotlin)
class MyVm(application: Application) : AndroidViewModel(application)
Esto funciona para la versión 2.0.0-alpha1
.
Escribí una biblioteca que debería hacer que lograr esto sea más sencillo y limpio, sin necesidad de multibindings o boilerplate de fábrica, al mismo tiempo que le ViewModel
la posibilidad de parametrizar aún más el ViewModel
en tiempo de ejecución: 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"
}
}
En la vista:
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
})
}
}
Si tienes parámetro en el constructor entonces:
DAGGER 2 constructor público para la dependencia @inject
@Inject
public UserViewModel(UserFacade userFacade)
{
this.userFacade = userFacade;
}
De lo contrario, la daga 2 le enviará el error "no se puede instanciar el objeto de modelo de vista"
ViewModelFactory
que nos proporcionará un ViewModel correcto de 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
es responsable de vincular todas las clases de ViewModel a
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
es una anotación para usar como clave en el mapa y se ve así
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@MapKey
@interface ViewModelKey {
Class<? extends ViewModel> value();
}
Ahora puede crear ViewModel y satisfacer todas las dependencias necesarias del gráfico
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;
}
}
Instalando 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);
}
}
Y no ViewModelModule
agregar ViewModelModule
en la lista de modules
@Singleton
@Component(modules = {ApplicationModule.class, ViewModelModule.class})
public interface ApplicationComponent {
//
}