studio - view android definicion
¿Cómo uso la clase AndroidInjection en vistas personalizadas u otras clases de Android? (4)
Mi problema con el patrón específico de Android es que, si utiliza su clase AndroidInjection
, no hay forma de que los miembros inyecten otros objetos además de Activities
/ Fragments
/ vistas / adaptadores personalizados, excepto con el Componente de la aplicación. Esto se debe a que no puede obtener una referencia del Subcomponent
( AndroidInjector
) utilizado para inyectar Activities
/ Fragments
. Esto hace que inyectar diálogos (si utiliza DialogFragments
).
La clase AndroidInjection
parece soportar solo los tipos básicos de Android.
Esto se debe a que no puede obtener una referencia del
Subcomponent
(AndroidInjector
) utilizado para inyectarActivities
/Fragments
.
Siempre se puede inyectar el propio componente. Simplemente agregue un campo para el componente a su Actividad / Fragmento y deje que Dagger lo inyecte junto con el resto.
// will be injected
@Inject MainActivitySubcomponent component;
El problema de si las clases dagger-android como AndroidInjector
deberían admitir la inyección dentro de Views o no se ha tratado en el siguiente problema de Github:
https://github.com/google/dagger/issues/720
Citando a uno de los autores de la biblioteca:
Hay tanto un punto filosófico como un punto logístico / de implementación que se hará aquí.
Primero, no está del todo claro para nosotros que inyectar vistas sea lo correcto. Los objetos de vista están destinados a dibujar, y no mucho más. El controlador (en un patrón MVC tradicional) es el que puede coordinar y pasar los datos apropiados a una vista. Inyectar una vista difumina las líneas entre los fragmentos y las vistas (¿quizás un fragmento secundario sea realmente el constructo apropiado?)
Desde la perspectiva de la implementación, también hay un problema en el hecho de que no hay una forma canónica de recuperar los Fragmentos principales de la Vista (si existe), o la Actividad para recuperar un componente principal. Se han sugerido algunos trucos para construir esa relación, pero hasta ahora no hemos visto nada que parezca sugerir que podamos hacer esto correctamente. Podríamos simplemente llamar a View.getContext (). GetApplicationContext () e inyectar desde allí, pero omitir las capas intermedias sin ninguna opción para algo intermedio es inconsistente con el resto de nuestro diseño, y probablemente sea confuso para los usuarios incluso si funciona.
Esto refuerza la opinión expresada en la respuesta de Vasily.
Para agregar más, las personas a menudo parecen querer inyectar dependencias de capa de modelo dentro de sus vistas personalizadas. Esta es una mala idea, ya que va en contra del principio de ingeniería de software de la separación de preocupaciones .
La solución correcta para asociar una vista y un modelo es escribir un adaptador como los adaptadores para RecyclerView y ListView. Puede inyectar la dependencia de la capa modelo en el nivel de Fragmento o Presentador y configurar el adaptador allí.
Lo que sigue no es una respuesta a su pregunta, sino una explicación de por qué no debería hacer esta pregunta.
Debe evitar las inyecciones en Views
personalizadas en general. Las razones de esto se enumeran en este artículo .
Las ventajas de usar el método de inyección en este caso [inyección en vistas personalizadas] son:
- Las dependencias deberán propagarse desde el componente de nivel superior (Actividad o Fragmento)
- Método de inyección no abre la puerta a la violación del principio de responsabilidad única
- No hay dependencia en el marco
- Mejor presentación
La primera ventaja podría ser una sorpresa, ya que la propagación desde un componente de nivel superior es más difícil que agregar anotaciones a los campos e involucra más código repetitivo. Esto seguramente es algo malo, ¿no ?. No en este caso. De hecho, hay dos aspectos positivos asociados con tal propagación de dependencias. En primer lugar, las dependencias serán visibles en el componente de nivel superior. Por lo tanto, solo mirando, por ejemplo, los campos de Fragmento, el lector del código entenderá inmediatamente que este Fragmento muestra imágenes. Estas optimizaciones para facilitar la lectura hacen que el sistema sea más fácil de mantener a largo plazo. En segundo lugar, no hay muchos casos de uso en los que las subclases de View necesiten dependencias adicionales. El hecho de que realmente necesite trabajar para proporcionar estas dependencias le dará un poco de tiempo para pensar si proporcionarlas es una buena decisión de diseño para comenzar.
La segunda ventaja está relacionada con la construcción colaborativa. Es posible que tenga experiencia como ingeniero de software, pero probablemente también tenga compañeros de equipo menos experimentados. O es posible que abandone el proyecto algún día, y el tipo que se hará cargo no será tan bueno como usted. Al inyectar una única dependencia utilizando un marco, básicamente abre una puerta para otras inyecciones. Imagine que algunos datos de SharedPreferences se requieren en la vista personalizada para, por ejemplo, corregir un error. Uno de los desarrolladores con menos experiencia podría decidir que es un buen método para inyectar SharedPreferences en la vista personalizada directamente. Hacer esto viola el Principio de Responsabilidad Única, pero es posible que el desarrollador ni siquiera sea consciente de tal concepto. Por lo tanto, a largo plazo, tales "puertas traseras" de inyección pueden reducir la calidad del diseño y llevar a largas sesiones de depuración.
La tercera ventaja de usar el método de inyección con vistas personalizadas es que no une la vista al marco de inyección de dependencias. Solo imagine que dentro de pocos años usted (o algún otro tipo pobre) necesita reemplazar el marco. El hecho de que probablemente tengas decenas de Actividades y Fragmentos para comenzar hará que tu vida sea miserable. Si tendrá que manejar más de diez o cientos de vistas personalizadas, entonces podría provocarle un estado de ánimo suicida.
La última (pero no menos importante) ventaja es el rendimiento. Una pantalla puede contener una actividad, varios fragmentos y decenas de vistas personalizadas. El arranque de este número de clases utilizando el marco de inyección de dependencias podría degradar el rendimiento de la aplicación. Es especialmente cierto para los marcos basados en la reflexión, pero incluso Dagger conlleva algún costo de rendimiento.
Además, aconsejo evitar el nuevo método de inyección que implica la clase AndroidInjection
. Se discute en este video tutorial .
Primero, debes pensar en la respuesta de Vasily .
Pero pensemos por un momento cómo hicimos esto antes de Dagger Android? Construimos un subcomponente a partir del componente que se tomó de la clase de Application
. Más adelante, podríamos usar este subcomponente para inyectar campos, por ejemplo, de una vista personalizada.
Entonces, trataremos de hacer exactamente lo mismo ahora.
Supongamos que nuestro objetivo es inyectar la clase MyAdapter
en un MyButton
:
public class MyButton extends AppCompatButton {
@Inject MyAdapter adapter;
public MyButton(Context context) {
super(context);
...
}
}
Y vamos a hacer que el adaptador tenga una dependencia del Context
la actividad, no del Context
aplicación:
public class MyAdapter {
@Inject
public MyAdapter(@Named("activity") Context context) {
}
}
Vamos a empezar con la clase de Application
personalizada.
MyApplication.java
public class MyApplication extends DaggerApplication {
@Inject
DispatchingAndroidInjector<Activity> dispatchingActivityInjector;
public static MySubcomponent mySubcomponent;
@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return DaggerAppComponent.builder()
.create(this);
}
}
AppComponent.java :
@Component(modules = {AndroidSupportInjectionModule.class, ActivityBindingModule.class, AppModule.class})
@Singleton
public interface AppComponent extends AndroidInjector<MyApplication> {
@Component.Builder
abstract class Builder extends AndroidInjector.Builder<MyApplication> {
}
}
AppModule.java
@Module
abstract class AppModule {
@Binds
@Singleton
@Named("app")
abstract Context providesContext(Application application);
}
ActivityBindingModule.java
@Module(subcomponents = MySubcomponent.class)
public abstract class ActivityBindingModule {
@Binds
@IntoMap
@ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory<? extends Activity>
bindMainActivityInjectorFactory(MySubcomponent.Builder builder);
}
AndroidSupportInjectionModule.java
se envía con la propia daga. Si no usa clases del paquete de soporte (es decir, android.support.v4.app.Fragment
lugar de android.app.Fragment
), use AndroidInjectionModule.java
.
MySubcomponent.java
@ActivityScope
@Subcomponent(modules = {SubcomponentModule.class/*, other modules here, if needed */})
public interface MySubcomponent extends AndroidInjector<MainActivity> {
void inject(MyButton button);
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {
public abstract MySubcomponent build();
}
}
SubcomponentModule.java
@Module
abstract class SubcomponentModule {
@Binds
@ActivityScope
@Named("activity")
abstract Context toContext(MainActivity activity);
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Inject
MySubcomponent subcomponent;
@Override
protected void onCreate(Bundle savedInstanceState) {
// Will inject `subcomponent` field
AndroidInjection.inject(this);
// Saving this component in a static field
// Hereafter you are taking responsibility of mySubcomponent lifetime
MyApplication.mySubcomponent = subcomponent;
super.onCreate(savedInstanceState);
setContentView(new MyButton(this));
}
}
Teniendo todo esto, ahora aquí está cómo se verá MyButton
:
public class MyButton extends AppCompatButton {
@Inject MyAdapter adapter;
public MyButton(Context context) {
super(context);
MyApplication.mySubcomponent.inject(this);
}
}
Admito que esto se ve complicado y, ciertamente, no es un enfoque para atenerse. Estoy feliz de ver un mejor enfoque.