android android-lifecycle dagger-2

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); } } }