Join FlipAndroid.COM Telegram Group: https://t.me/joinchat/F_aqThGkhwcLzmI49vKAiw


Dagger- ¿Debemos crear cada componente y módulo para cada Actividad / Fragmento

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

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

  • Dagger2 no genera archivos "Dagger_"
  • ¿Cómo evitar la adición de método de inyección para cada vista?
  • ¿La manera más simple de crear un Singleton w / Dagger 2?
  • No se puede encontrar la clase de símbolo "Generated" para Dagger 2
  • Dagger 2 - módulos de diferentes componentes
  • ¿Puede alguien dar una buena explicación de Dagger 2?
  • En mi opinión, por lo general deberíamos organizar el código como este:

    1. Cree un AppComponent y AppModule que proporcionará todos los objetos que se usará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. Normalmente los pondré como clases estáticas dentro de la clase Activity:

       @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 ejemplos muy sencillos para mostrar cómo implementaría esto.

    Pero un amigo mío acaba de darme otra implementación:

    1. Crear 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 mis amigos no es del todo buena, pero por favor, corrija si estoy equivocado. Estas son las razones:

    1. Una gran cantidad de fugas de memoria :

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

    3. Tomará mucho tiempo para que la aplicación se inicialice (porque tiene que crear muchos presentadores, objetos, …)

    Lo siento por un largo post, pero por favor ayúdame a aclarar esto para mí y mi amigo, no puedo convencerlo. Sus comentarios serán muy apreciados.

    / ————————————————- Unesdoc.unesco.org unesdoc.unesco.org

    Editar después de hacer una demostración.

    En primer lugar, gracias por @pandawarrior respuesta. 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 fugas de memoria a menos que ponga cualquier 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. (Por lo tanto, mi segundo punto es incorrecto, también)
    3. Dagger creará los presentadores sólo cuando sean necesarios. (Por lo tanto, la aplicación no tardará mucho tiempo en inicializar, yo estaba confundido por Lazy Injection)

    Por lo tanto, todas las razones que he dicho anteriormente son en su mayoría mal. Pero no significa que debamos seguir la idea de mi amigo, por dos razones:

    1. No es bueno para la arquitectura de la fuente, cuando él inits todos los presentadores en el módulo / componente. (Que viola el principio de segregación de interfaz , tal vez principio de Responsabilidad Única, también).

    2. Cuando creamos un componente de ámbito, sabremos cuándo se crea y cuándo se destruye, que es un gran beneficio para evitar fugas de memoria. Por lo tanto, para cada actividad debemos crear un componente con un @ActivityScope. Imaginemos, con la implementación de mis amigos, que nos olvidamos de poner algún Alcance en el método Proveedor => se producirán fugas de memoria.

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

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

    Y una nota más: Si quieres ver cuando el objeto se destruye, puedes llamar a los de método juntos y el GC se ejecutará inmediatamente:

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

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

  • ¿Textura al revés? | OpenGL-ES 2.0 (Android)
  • ¿Cómo habilitar la depuración USB en Android?
  • Android "Not_Market_Managed" error
  • Problema de empaquetado de Android: resources.ap_ no existe
  • Accediendo simultáneamente a diferentes miembros del mismo objeto en Java
  • Eclipse no reconocerá el proyecto como biblioteca (ActionBarSherlock / ViewPagerIndicator)
  • 4 Solutions collect form web for “Dagger- ¿Debemos crear cada componente y módulo para cada Actividad / Fragmento”

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

    Sin embargo, tener sólo un componente que está vinculado al ciclo de vida de la Application y usarlo para inyección en todas las Activities tampoco es la solución óptima (este es el enfoque de su amigo). No es óptimo porque:

    1. Se limita a un solo ámbito ( @Singleton o uno personalizado)
    2. El único ámbito al que está restringido hace que los objetos inyectados sean "singletons de aplicación", por lo que los errores en el escopo o el uso incorrecto de los objetos de ámbito pueden causar fácilmente pérdidas de memoria globales
    3. Usted querrá usar Dagger2 para inyectarse en Services también, pero los Services pueden requerir diferentes objetos que Activities (por ejemplo, los Services no necesitan presentadores, no tienen FragmentManager , etc.). Usando un solo componente pierdes la flexibilidad de definir diferentes gráficos de objetos para diferentes componentes.

    Por lo tanto, un componente por Activity es un exceso, pero un solo componente para toda la aplicación no es lo suficientemente flexible. La solución óptima se encuentra entre estos extremos (como suele ser).

    Utilizo el siguiente enfoque:

    1. Componente "application" único 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 objetos que son requeridos por todos los "controladores" orientados al usuario (en mi arquitectura son Activities y Fragments ). Instanciado en cada Activity y Fragment .
    3. Subcomponente "Servicio" del componente "aplicación" que proporciona los objetos que son requeridos por todos los Services . Instanciado en cada Service .

    A continuación se muestra un ejemplo de cómo podría implementar el mismo enfoque.


    Editar Julio 2017

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


    Ámbito de 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 la inyección de dependencia:

    Dagger 2 Scopes Demystified

    Inyección de dependencia en Android

    La primera opción crea un componente subscoped para cada actividad, donde la actividad es capaz de crear componentes subscoped que sólo proporcionan la dependencia (presentador) para esa actividad en particular.

    La segunda opción crea un componente @Singleton único que puede proporcionar a los presentadores como dependencias sin escáner, es decir, cuando se accede a ellas, se crea una nueva instancia del presentador cada vez. (No, no crea una nueva instancia hasta que solicite una).


    Técnicamente, ninguno de los dos enfoques es peor que el otro. La primera aproximación no separa a los presentadores por características, sino por capas.

    He utilizado ambos, ambos trabajan y ambos tienen sentido.

    La única desventaja de la primera solución (si está utilizando @Component(dependencies={...} lugar de @Subcomponent ) es que necesita asegurarse de que no es la actividad que crea su propio módulo internamente, porque entonces no se puede Reemplazar implementaciones de método de módulo con mocks.A continuación, si utiliza la inyección de constructor en lugar de la inyección de campo, puede crear la clase directamente con el constructor, dando directamente mocks.

    Algunos de los mejores ejemplos de cómo organizar sus componentes, módulos y paquetes se pueden encontrar en los Repuestos de Github de Arquitectura de Arquitectura de Google para Android aquí .

    Si examina el código fuente allí, puede ver que hay un único componente con escopo de aplicación (con un ciclo de vida de la duración de toda la aplicación) y, a continuación, separar los componentes con escopo de actividad para la actividad y el fragmento correspondientes a una funcionalidad dada en una proyecto. Por ejemplo, existen los siguientes paquetes:

     addedittask taskdetail tasks 

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

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

    La ventaja de organizarse 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 el elemento eficaz de Java 13. En otras palabras, las clases agrupadas funcionalmente estarán en el mismo Y puede aprovechar package-private modificadores de accesibilidad protected y de package-private para evitar usos no deseados de sus clases.

    Tu amigo es correcto, realmente no tienes que crear componentes y módulos para cada actividad. Se supone que Dagger te ayuda a reducir el código desordenado y hace que tus actividades de Android sean más limpias al delegar instantiations de clase a los Módulos en lugar de instanciarlos en el método onCreate de Activities.

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

    Así que escribir demasiadas cosas tipo de derrotar el propósito de la daga no? Prefiero instanciar a mis presentadores en Actividades si tengo que crear Módulos y Componentes para cada Actividades.

    En cuanto a sus preguntas sobre:

    1- Fuga de memoria:

    No, no a menos que ponga una anotación @Singleton a los presentadores que usted proporciona. Dagger sólo creará el objeto siempre que hagas un @Inject en la clase de destino`. No creará los otros presentadores en su escenario. Puede intentar utilizar el registro para ver si se crean 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- Usted se inyecta dos veces y registra su código de 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 sólo se crearán cuando usted @Inject en las actividades, en lugar de la aplicación init.

    FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.