Biblioteca de ciclo de vida de Android ViewModel using dagger 2
viewmodel dagger 2 (5)
Tengo una clase ViewModel como la definida en la sección Conexión de ViewModel y repositorio de la guía de Arquitectura . Cuando ejecuto mi aplicación obtengo una excepción en tiempo de ejecución. ¿Alguien sabe cómo evitar esto? ¿No debería estar inyectando ViewModel? ¿Hay alguna manera de decirle a ViewModelProvider
que use Dagger para crear el modelo?
public class DispatchActivityModel extends ViewModel {
private final API api;
@Inject
public DispatchActivityModel(API api) {
this.api = api;
}
}
Causado por: java.lang.InstantiationException: java.lang.Class no tiene constructor de argumentos cero en java.lang.Class.newInstance (método nativo) en android.arch.lifecycle.ViewModelProvider $ NewInstanceFactory.create (ViewModelProvider.java:143) en android.arch.lifecycle.ViewModelProviders $ DefaultFactory.create (ViewModelProviders.java:143) en android.arch.lifecycle.ViewModelProvider.get (ViewModelProvider.java:128) en android.arch.lifecycle.ViewModelProvider.get (ViewModelProvider.java : 96) en com.example.base.BaseActivity.onCreate (BaseActivity.java:65) en com.example.dispatch.DispatchActivity.onCreate (DispatchActivity.java:53) en android.app.Activity.performCreate (Activity.java: 6682) en android.app.Instrumentation.callActivityOnCreate (Instrumentation.java:1118) en android.app.ActivityThread.performLaunchActivity (ActivityThread.java:2619) en android.app.ActivityThread.handleLaunchActivity (ActivityThread.java:2727) en android. app.ActivityThread.-wrap12 (ActivityThread.java) en android.app.ActivityT hread $ H.handleMessage (ActivityThread.java:1478) en android.os.Handler.dispatchMessage (Handler.java:102) en android.os.Looper.loop (Looper.java:154) en android.app.ActivityThread.main (ActivityThread.java:6121)
Creo que hay una segunda opción si no quieres usar la fábrica mencionada en la respuesta de Robert. No es necesariamente una mejor solución, pero siempre es bueno conocer las opciones.
Puede dejar viewModel con el constructor predeterminado e inyectar sus dependencias tal como lo hace en el caso de las actividades u otros elementos creados por el sistema. Ejemplo:
ViewModel:
public class ExampleViewModel extends ViewModel {
@Inject
ExampleDependency exampleDependency;
public ExampleViewModel() {
DaggerExampleComponent.builder().build().inject(this);
}
}
Componente:
@Component(modules = ExampleModule.class)
public interface ExampleComponent {
void inject(ExampleViewModel exampleViewModel);
}
Módulo:
@Module
public abstract class ExampleModule {
@Binds
public abstract ExampleDependency bindExampleDependency(ExampleDependencyDefaultImplementation exampleDependencyDefaultImplementation);
}
Saludos, Piotr
Hoy aprendí una forma de evitar tener que escribir fábricas para mis clases de ViewModel
:
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
}
EDITAR: Como señaló @Calin en los comentarios, estamos usando Dagger''s Lazy
en el fragmento de código anterior, no en Kotlin''s.
En lugar de inyectar ViewModel
, puede inyectar un ViewModelFactory
genérico en sus actividades y fragmentos y obtener una instancia de cualquier 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)
...
}
...
}
AndroidInjection.inject(this)
como con la biblioteca dagger-android
, pero puedes inyectar tu actividad o fragmento de la manera que prefieras. Todo lo que queda es asegurarse de proporcionar su ViewModel
desde un módulo:
@Module
object MyModule {
@JvmStatic
@Provides
fun myViewModel(someDependency: SomeDependency) = MyViewModel(someDependency)
}
O aplicando la anotación @Inject
a su constructor:
class MyViewModel @Inject constructor(
someDependency: SomeDependency
) : ViewModel() {
...
}
Lo que puede no ser obvio en la pregunta es que ViewModel no se puede inyectar de esa manera porque la fábrica predeterminada de ViewModelProvider que se obtiene de la
ViewModelProvider.of(LifecycleOwner lo)
El método con solo el parámetro LifecycleOwner solo puede instanciar un ViewModel que tiene un constructor predeterminado no arg.
Usted tiene un param: ''api'' en su constructor:
public DispatchActivityModel(API api) {
Para hacer eso, necesitas crear una fábrica para que puedas decir cómo crearla. El código de muestra de google le da la configuración de Dagger y el código de fábrica como se menciona en la respuesta aceptada.
DI se creó para evitar el uso del operador new () en las dependencias porque si las implementaciones cambian, cada referencia debería cambiar también. La implementación de ViewModel usa prudentemente un patrón de fábrica estático ya con ViewProvider.of (). Get () que hace que su inyección sea innecesaria en el caso del constructor no-arg. Entonces, en el caso de que no necesite escribir la fábrica, no necesita inyectar una fábrica, por supuesto.
Me gustaría ofrecer una tercera opción para cualquiera que tropiece con esta pregunta. La biblioteca Dagger ViewModel le permitirá inyectar de forma similar a Dagger2 con ViewModels especificando opcionalmente el alcance de ViewModel.
Elimina una gran cantidad de la plantilla estándar y también permite que ViewModels se inyecte de forma declarativa con una anotación:
@InjectViewModel(useActivityScope = true)
public MyFragmentViewModel viewModel;
También requiere una pequeña cantidad de código para configurar un módulo desde el cual se pueden generar los ViewModels totalmente inyectados y después de eso es tan simple como llamar:
void injectFragment(Fragment fragment, ViewModelFactory factory) {
ViewModelInejectors.inject(frag, viewModelFactory);
}
En la clase ViewModelInjectors que se genera.
DESCARGO DE RESPONSABILIDAD: es mi biblioteca, pero creo que también es útil para el autor de esta pregunta y para cualquier otra persona que desee lograr lo mismo.
ViewModelProvider.Factory
implementar su propio ViewModelProvider.Factory
. Hay una aplicación de ejemplo creada por Google que demuestra cómo conectar Dagger 2 con ViewModels. LINK . Necesitas esas 5 cosas:
En ViewModel:
@Inject
public UserViewModel(UserRepository userRepository, RepoRepository repoRepository) {
Definir anotación:
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@MapKey
@interface ViewModelKey {
Class<? extends ViewModel> value();
}
En ViewModelModule:
@Module
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(UserViewModel.class)
abstract ViewModel bindUserViewModel(UserViewModel userViewModel);
En Fragmento:
@Inject
ViewModelProvider.Factory viewModelFactory;
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
userViewModel = ViewModelProviders.of(this, viewModelFactory).get(UserViewModel.class);
Fábrica:
@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);
}
}
}