support implement google compiler annotationprocessor android dagger dagger-2

android - implement - Daga: ¿deberíamos crear cada componente y módulo para cada Actividad/Fragmento?



dagger android support (4)

Algunos de los mejores ejemplos de cómo organizar sus componentes, módulos y paquetes se pueden encontrar en el repositorio de Google Android Architecture Blueprints Github here .

Si examina el código fuente allí, puede ver que hay un solo componente con ámbito de aplicación (con un ciclo de vida de la duración de toda la aplicación) y luego componentes separados con ámbito de actividad para la actividad y el fragmento correspondiente a una funcionalidad dada en un proyecto. Por ejemplo, hay los siguientes paquetes:

addedittask taskdetail tasks

Dentro de cada paquete hay un módulo, componente, presentador, etc. Por ejemplo, dentro del taskdetail hay las siguientes clases:

TaskDetailActivity.java TaskDetailComponent.java TaskDetailContract.java TaskDetailFragment.java TaskDetailPresenter.java TaskDetailPresenterModule.java

La ventaja de organizar de esta manera (en lugar de agrupar todas las actividades en un componente o módulo) es que puede aprovechar los modificadores de accesibilidad de Java y cumplir con el elemento 13. Efectivo de Java. En otras palabras, las clases agrupadas funcionalmente estarán en el mismo paquete y puede aprovechar package-private modificadores de accesibilidad protected y package-private para evitar usos no deseados de sus clases.

He estado trabajando con dagger2 por un tiempo. Y me confundí entre crear un componente / módulo propio para cada Actividad / Fragmento. Por favor, ayúdame a aclarar esto:

Por ejemplo, tenemos una aplicación, y la aplicación tiene aproximadamente 50 pantallas. Implementaremos el código siguiendo el patrón MVP y Dagger2 para DI. Supongamos que tenemos 50 actividades y 50 presentadores.

En mi opinión, generalmente deberíamos organizar el código así:

  1. Cree un AppComponent y AppModule que proporcionará todos los objetos que se utilizarán mientras la aplicación esté abierta.

    @Module public class AppModule { private final MyApplicationClass application; public AppModule(MyApplicationClass application) { this.application = application; } @Provides @Singleton Context provideApplicationContext() { return this.application; } //... and many other providers } @Singleton @Component( modules = { AppModule.class } ) public interface AppComponent { Context getAppContext(); Activity1Component plus(Activity1Module module); Activity2Component plus(Activity2Module module); //... plus 48 methods for 48 other activities. Suppose that we don''t have any other Scope (like UserScope after user login, ....) }

  2. Crear ActivityScope:

    @Scope @Documented @Retention(value=RUNTIME) public @interface ActivityScope { }

  3. Crear componente y módulo para cada actividad. Por lo general, los pondré como clases estáticas dentro de la clase Actividad:

    @Module public class Activity1Module { public LoginModule() { } @Provides @ActivityScope Activity1Presenter provideActivity1Presenter(Context context, /*...some other params*/){ return new Activity1PresenterImpl(context, /*...some other params*/); } } @ActivityScope @Subcomponent( modules = { Activity1Module.class } ) public interface Activity1Component { void inject(Activity1 activity); // inject Presenter to the Activity } // .... Same with 49 remaining modules and components.

Esos son solo ejemplos muy simples para mostrar cómo implementaría esto.

Pero un amigo mío me dio otra implementación:

  1. Cree PresenterModule que proporcionará a todos los presentadores:

    @Module public class AppPresenterModule { @Provides Activity1Presenter provideActivity1Presentor(Context context, /*...some other params*/){ return new Activity1PresenterImpl(context, /*...some other params*/); } @Provides Activity2Presenter provideActivity2Presentor(Context context, /*...some other params*/){ return new Activity2PresenterImpl(context, /*...some other params*/); } //... same with 48 other presenters. }

  2. Crear AppModule y AppComponent:

    @Module public class AppModule { private final MyApplicationClass application; public AppModule(MyApplicationClass application) { this.application = application; } @Provides @Singleton Context provideApplicationContext() { return this.application; } //... and many other provides } @Singleton @Component( modules = { AppModule.class, AppPresenterModule.class } ) public interface AppComponent { Context getAppContext(); public void inject(Activity1 activity); public void inject(Activity2 activity); //... and 48 other methods for 48 other activities. Suppose that we don''t have any other Scope (like UserScope after user login, ....) }

Su explicación es: no tiene que crear componentes y módulos para cada actividad. Creo que la idea de mi amigo no es del todo buena, pero corríjame si me equivoco. Aquí están las razones:

  1. Mucha pérdida de memoria :

    • La aplicación creará 50 presentadores incluso si el usuario solo tiene 2 actividades abiertas.
    • Después de que el usuario cierre una actividad, su presentador seguirá siendo
  2. ¿Qué sucede si quiero crear dos instancias de una actividad? (¿Cómo puede crear dos presentadores?)

  3. La aplicación tardará mucho tiempo en inicializarse (porque tiene que crear muchos presentadores, objetos, ...)

Perdón por una publicación larga, pero por favor ayúdame a aclarar esto para mí y mi amigo, no puedo convencerlo. Tus comentarios serán muy apreciados.

/ ------------------------------------------------- ---------------------- /

Edite después de hacer una demostración.

Primero, gracias por la respuesta de @pandawarrior. Debería haber creado una demostración antes de hacer esta pregunta. Espero que mi conclusión aquí pueda ayudar a alguien más.

  1. Lo que mi amigo ha hecho no causa pérdidas de memoria a menos que ponga un Alcance a los métodos Provides. (Por ejemplo, @Singleton o @UserScope, ...)
  2. Podemos crear muchos presentadores, si el método Provides no tiene ningún Alcance. (Entonces, mi segundo punto también está mal)
  3. Dagger creará los presentadores solo cuando sean necesarios. (Entonces, la aplicación no tardará mucho en inicializarse, me confundió Lazy Injection)

Entonces, todas las razones que he dicho anteriormente son en su mayoría incorrectas. Pero no significa que debamos seguir mi idea de amigo, por dos razones:

  1. No es bueno para la arquitectura de la fuente, cuando inicia a todos los presentadores en el módulo / componente. (Viola el principio de segregación de interfaz , quizás también el principio de responsabilidad única).

  2. Cuando creamos un componente de alcance, sabremos cuándo se creó y cuándo se destruyó, lo cual es un gran beneficio para evitar pérdidas de memoria. Entonces, para cada Actividad debemos crear un Componente con un @ActivityScope. Imaginemos, con la implementación de mis amigos, que olvidamos poner algo de alcance en el método del proveedor => ocurrirán pérdidas de memoria.

En mi opinión, con una aplicación pequeña (solo unas pocas pantallas sin muchas dependencias o con dependencias similares), podríamos aplicar la idea de mis amigos, pero por supuesto no es recomendable.

Prefiere leer más sobre: ¿Qué determina el ciclo de vida de un componente (gráfico de objeto) en Dagger 2? Alcance de la actividad de Dagger2, ¿cuántos módulos / componentes necesito?

Y una nota más: si desea ver cuándo se destruyen los objetos, puede llamar a los del método juntos y el GC se ejecutará de inmediato:

System.runFinalization(); System.gc();

Si usa solo uno de estos métodos, GC se ejecutará más tarde y puede obtener resultados incorrectos.


Declarar un módulo separado para cada Activity no es una buena idea en absoluto. Declarar un componente separado para cada Activity es aún peor. El razonamiento detrás de esto es muy simple: realmente no necesita todos estos módulos / componentes (como ya lo ha visto usted mismo).

Sin embargo, tener un solo componente relacionado con el ciclo de vida de la aplicación y usarlo para inyectarlo en todas las Activities tampoco es la solución óptima (este es el enfoque de su amigo). No es óptimo porque:

  1. Te restringe a un solo alcance ( @Singleton o uno personalizado)
  2. El único alcance al que está restringido hace que los objetos inyectados sean "aplicaciones únicas", por lo tanto, los errores en el alcance o el uso incorrecto de los objetos con alcance pueden causar fácilmente pérdidas de memoria global.
  3. También querrá usar Dagger2 para inyectar en los Services , pero los Services pueden requerir diferentes objetos que las Activities (por ejemplo, los Services no necesitan presentadores, no tienen FragmentManager , etc.). Al usar un solo componente, pierde la flexibilidad de definir diferentes gráficos de objetos para diferentes componentes.

Por lo tanto, un componente por Activity es una exageración, pero un componente único para toda la aplicación no es lo suficientemente flexible. La solución óptima está entre estos extremos (como suele ser).

Yo uso el siguiente enfoque:

  1. Componente único de "aplicación" que proporciona objetos "globales" (por ejemplo, objetos que contienen un estado global que se comparte entre todos los componentes de la aplicación). Instanciado en la Application .
  2. Subcomponente "Controlador" del componente "aplicación" que proporciona los objetos requeridos por todos los "controladores" orientados al usuario (en mi arquitectura, estas son Activities y Fragments ). Instanciado en cada Activity y Fragment .
  3. Subcomponente "Servicio" del componente "aplicación" que proporciona objetos requeridos por todos los Services . Instanciado en cada Service .

El siguiente es un ejemplo de cómo podría implementar el mismo enfoque.

Editar julio de 2017

Publiqué un video tutorial que muestra cómo estructurar el código de inyección de dependencia de Dagger en la aplicación de Android : Android Dagger for Professionals Tutorial .

Editar febrero 2018

Publiqué un curso completo sobre inyección de dependencia en Android .

En este curso explico la teoría de la inyección de dependencia y muestro cómo surge de forma natural en la aplicación de Android. Luego demuestro cómo las construcciones de Dagger se ajustan al esquema general de inyección de dependencia.

Si toma este curso, comprenderá por qué la idea de tener una definición separada de módulo / componente para cada Actividad / Fragmento es básicamente defectuosa en la forma más fundamental.

Tal enfoque hace que la estructura de la capa de presentación del conjunto de clases "Funcional" se refleje en la estructura del conjunto de clases de "Construcción", uniéndolos así. Esto va en contra del objetivo principal de la inyección de dependencia, que es mantener disjuntos los conjuntos de clases "Construcción" y "Funcional".

Alcance de la aplicación:

@ApplicationScope @Component(modules = ApplicationModule.class) public interface ApplicationComponent { // Each subcomponent can depend on more than one module ControllerComponent newControllerComponent(ControllerModule module); ServiceComponent newServiceComponent(ServiceModule module); } @Module public class ApplicationModule { private final Application mApplication; public ApplicationModule(Application application) { mApplication = application; } @Provides @ApplicationScope Application applicationContext() { return mApplication; } @Provides @ApplicationScope SharedPreferences sharedPreferences() { return mApplication.getSharedPreferences(Constants.PREFERENCES_FILE, Context.MODE_PRIVATE); } @Provides @ApplicationScope SettingsManager settingsManager(SharedPreferences sharedPreferences) { return new SettingsManager(sharedPreferences); } }

Alcance del controlador:

@ControllerScope @Subcomponent(modules = {ControllerModule.class}) public interface ControllerComponent { void inject(CustomActivity customActivity); // add more activities if needed void inject(CustomFragment customFragment); // add more fragments if needed void inject(CustomDialogFragment customDialogFragment); // add more dialogs if needed } @Module public class ControllerModule { private Activity mActivity; private FragmentManager mFragmentManager; public ControllerModule(Activity activity, FragmentManager fragmentManager) { mActivity = activity; mFragmentManager = fragmentManager; } @Provides @ControllerScope Context context() { return mActivity; } @Provides @ControllerScope Activity activity() { return mActivity; } @Provides @ControllerScope DialogsManager dialogsManager(FragmentManager fragmentManager) { return new DialogsManager(fragmentManager); } // @Provides for presenters can be declared here, or in a standalone PresentersModule (which is better) }

Y luego en Activity :

public class CustomActivity extends AppCompatActivity { @Inject DialogsManager mDialogsManager; private ControllerComponent mControllerComponent; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getControllerComponent().inject(this); } private ControllerComponent getControllerComponent() { if (mControllerComponent == null) { mControllerComponent = ((MyApplication)getApplication()).getApplicationComponent() .newControllerComponent(new ControllerModule(this, getSupportFragmentManager())); } return mControllerComponent; } }

Información adicional sobre inyección de dependencia:

Daga 2 ámbitos desmitificados

Inyección de dependencia en Android


La primera opción crea un componente de sub-ámbito para cada actividad, donde la actividad puede crear componentes de sub-ámbito que solo proporcionan la dependencia (presentador) para esa actividad en particular.

La segunda opción crea un único componente @Singleton que puede proporcionar a los presentadores como dependencias sin ámbito, lo que significa que cuando accede a ellos, crea una nueva instancia del presentador cada vez. (No, no crea una nueva instancia hasta que solicite una).

Técnicamente, ninguno de los enfoques es peor que el otro. El primer enfoque no separa a los presentadores por característica, sino por capa.

He usado ambos, ambos funcionan y ambos tienen sentido.

La única desventaja de la primera solución (si está utilizando @Component(dependencies={...} lugar de @Subcomponent ) es que debe asegurarse de que no es la actividad la que crea su propio módulo internamente, porque entonces no puede reemplace las implementaciones del método del módulo con simulacros. De nuevo, si usa la inyección de constructor en lugar de la inyección de campo, puede crear la clase directamente con el constructor, dándole simulacros directamente.


Tu amigo tiene razón, realmente no tienes que crear componentes y módulos para cada actividad. Se supone que Dagger lo ayuda a reducir el código desordenado y hace que sus actividades de Android sean más limpias al delegar las instancias de clase en los Módulos en lugar de instanciarlas en el método onCreate de Actividades.

Normalmente haremos así

public class MainActivity extends AppCompatActivity { Presenter1 mPresenter1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mPresenter1 = new Presenter1(); // you instantiate mPresentation1 in onCreate, imagine if there are 5, 10, 20... of objects for you to instantiate. } }

Haces esto en su lugar

public class MainActivity extends AppCompatActivity { @Inject Presenter1 mPresenter1; // the Dagger module take cares of instantiation for your @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); injectThisActivity(); } private void injectThisActivity() { MainApplication.get(this) .getMainComponent() .inject(this); }}

Entonces, ¿escribir demasiadas cosas como una derrota con el propósito de la daga no? Prefiero crear instancias de mis presentadores en Actividades si tengo que crear Módulos y Componentes para cada Actividad.

En cuanto a sus preguntas sobre:

1- Pérdida de memoria:

No, a menos que ponga una anotación @Singleton a los presentadores que proporcione. Dagger solo creará el objeto cada vez que hagas un @Inject en la clase objetivo`. No creará los otros presentadores en su escenario. Puede intentar usar Log para ver si se crearon o no.

@Module public class AppPresenterModule { @Provides @Singleton // <-- this will persists throughout the application, too many of these is not good Activity1Presenter provideActivity1Presentor(Context context, ...some other params){ Log.d("Activity1Presenter", "Activity1Presenter initiated"); return new Activity1PresenterImpl(context, ...some other params); } @Provides // Activity2Presenter will be provided every time you @Inject into the activity Activity2Presenter provideActivity2Presentor(Context context, ...some other params){ Log.d("Activity2Presenter", "Activity2Presenter initiated"); return new Activity2PresenterImpl(context, ...some other params); } .... Same with 48 others presenters.

}

2- Inyecta dos veces y registra su código hash

//MainActivity.java @Inject Activity1Presenter mPresentation1 @Inject Activity1Presenter mPresentation2 @Inject Activity2Presenter mPresentation3 @Inject Activity2Presenter mPresentation4 //log will show Presentation2 being initiated twice @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); injectThisActivity(); Log.d("Activity1Presenter1", mPresentation1.hashCode()); Log.d("Activity1Presenter2", mPresentation2.hashCode()); //it will shows that both have same hash, it''s a Singleton Log.d("Activity2Presenter1", mPresentation3.hashCode()); Log.d("Activity2Presenter2", mPresentation4.hashCode()); //it will shows that both have different hash, hence different objects

3. No, los objetos solo se crearán cuando @Inject a las actividades, en lugar del inicio de la aplicación.