java - medium - dagger android support
¿Cómo organizas tus módulos y componentes Dagger 2? (2)
¿Tienes un paquete específico donde pones todas las clases relacionadas con Dagger?
¿O los MainActivityModule
al lado de la clase relevante que inyectan? Por ejemplo, si tienes un MainActivityModule
y MainActivityComponent
, los pones en el mismo paquete que tu MainActivity
.
Además, he visto bastantes personas que definen componentes como clases internas, por ejemplo, un ApplicationComponent
que se define dentro de la clase Application
. ¿Crees que esta es una buena práctica?
¿Tienes un paquete específico donde pones todas las clases relacionadas con Dagger?
¿O los colocas al lado de la clase relevante que inyectan? Por ejemplo, si tienes un MainActivityModule y MainActivityComponent, los pones en el mismo paquete que tu MainActivity.
No tengo mucha experiencia con eso, pero puedo mostrarte mi enfoque. Tal vez algunas personas con más experiencia pueden mejorar esa solución o proporcionar su punto de vista.
Normalmente organizo clases de Dagger 2 así:
- di
|
+-- ApplicationComponent class
|
+-- modules
|
+-- AndroidModule class
|
+-- WebServiceModule class
|
+-- ...
|
-
di
paquetedi
contiene clases relacionadas con Dagger 2 y la inyección de dependencia. - en la mayoría de los casos, la aplicación de Android generalmente tiene un Componente, aquí se llama
ApplicationComponent
. Aún no he creado una aplicación de Android con muchos componentes de Dagger 2 y he visto soluciones con solo un componente. -
modules
paquete demodules
contiene módulos Dagger 2
No creo módulo por Actividad. Módulos de funcionalidad específica del grupo. Por ejemplo, los elementos fuertemente conectados con el sistema como interfaz para SharedPreferences, EventBus (si está usando algo así), conectividad de red, etc. pueden estar ubicados en AndroidModule
. Si su proyecto tiene interfaces importantes para WebService o hay muchas de ellas, puede agruparlas en WebServiceModule
. Si su aplicación es, por ejemplo, responsable de analizar la red y tiene muchas interfaces para tareas similares relacionadas con la red, puede agrupar estas interfaces en NetworkModule
. Cuando su aplicación es simple, puede suceder que tenga solo un módulo. Cuando es complicado, puedes tener muchos módulos. En mi opinión, no deberías tener muchas interfaces en un solo módulo. Cuando hay tal situación, puede considerar dividirlos en módulos separados. También puede mantener cierta lógica comercial específica para su proyecto en un módulo separado.
Además, he visto bastantes personas que definen componentes como clases internas, por ejemplo, un ApplicationComponent que se define dentro de la clase Application. ¿Crees que esta es una buena práctica?
No estoy seguro de si es una buena o mala práctica. Creo que no hay necesidad de hacer eso. Puede crear un método público público get()
dentro de la clase que extiende la clase de Application
, que devolverá la instancia de la Application
como singleton. Es una solución mucho más simple y deberíamos tener solo una instancia de una clase de Application
. Si queremos simular el Contexto en pruebas unitarias, podemos aceptar el Contexto como parámetro y, en el código de una aplicación, pasar el Contexto de la Aplicación o el Contexto de la Actividad dependiendo de la situación.
Tenga en cuenta que es solo mi enfoque y algunos desarrolladores más experimentados pueden organizar sus proyectos de una manera diferente y mejor.
EDITAR: Permítanme comenzar con el hecho de que esto está cerca de la verdad aquí, pero este es un antipatrón como lo describe el artículo AQUÍ (CLICK THE LINK!) De Data Domain Presentation Layering
Martin Fowler, que especifica que no debe tener un MapperModule
y un PresenterModule
, usted debe tener un GalleryModule
y un SomeFeatureModule
que tenga todos los mapeadores, presentadores, etc. en él.
La ruta inteligente para hacerlo es usar dependencias de componentes para subcategorizar su componente singleton original para cada característica que tenga. Esto que describí es la estratificación de "pila completa" , separación por características .
Lo escrito a continuación es el "antipatrón", donde se cortan los módulos de nivel superior de su aplicación en "capas". Tiene numerosas desventajas para hacerlo. No lo hagas Pero puedes leerlo y aprender qué no hacer.
TEXTO ORIGINAL:
Normalmente, usaría un Component
único como un Component
ApplicationComponent
para contener todas las dependencias simples que use en toda la aplicación, siempre y cuando exista toda la aplicación. Debería crear una instancia de esto en su clase de Aplicación y hacer que sea accesible desde otro lugar.
La estructura del proyecto para mí actualmente es:
+ injection
|- components
|-- ApplicationComponent.java
|- modules
|- data
|-- DbMapperModule.java
|-- ...
|- domain
|-- InteractorModule.java
|-- ...
|- presentation
|-- ...
|- utils
|-- ...
|- scope
|- subcomponents
|- data
|-- ...
|- domain
|-- DbMapperComponent.java
|-- ...
|- presentation
|-- ...
|- utils
|-- ...
|-- AppContextComponent.java
|-- AppDataComponent.java
|-- AppDomainComponent.java
|-- AppPresentationComponent.java
|-- AppUtilsComponent.java
Por ejemplo, el mío es así:
public enum Injector {
INSTANCE;
private ApplicationComponent applicationComponent;
private Injector() {
}
public ApplicationComponent getApplicationComponent() {
return applicationComponent;
}
ApplicationComponent initializeApplicationComponent(CustomApplication customApplication) {
AppContextModule appContextModule = new AppContextModule(customApplication);
RealmModule realmModule = new RealmModule(customApplication.getRealmHolder());
applicationComponent = DaggerApplicationComponent.builder()
.appContextModule(appContextModule)
.realmModule(realmModule)
.build();
return applicationComponent;
}
}
Y necesita un ApplicationComponent
que pueda inyectar en los campos protegidos por paquetes de cualquier clase en la que desee inyectar el campo.
@Singleton
@Component(modules = {
AppContextModule.class,
DbMapperModule.class,
DbTaskModule.class,
RealmModule.class,
RepositoryModule.class,
InteractorModule.class,
ManagerModule.class,
ServiceModule.class,
PresenterModule.class,
JobManagerModule.class,
XmlPersisterModule.class
})
public interface ApplicationComponent
extends AppContextComponent, AppDataComponent, AppDomainComponent, AppUtilsComponent, AppPresentationComponent {
void inject(CustomApplication customApplication);
void inject(DashboardActivity dashboardActivity);
...
}
Para mí, AppContextComponent
sería un @Subcomponent
, pero eso no es lo que realmente significa. Esas son solo una forma de crear un subámbito, y no una forma de cortar el componente en partes más pequeñas. Entonces la interfaz que heredo es en realidad solo una interface
normal con métodos de provisión. Lo mismo para los demás.
public interface AppContextComponent {
CustomApplication customApplication();
Context applicationContext();
AppConfig appConfig();
PackageManager packageManager();
AlarmManager alarmManager();
}
Las dependencias de los componentes (que le permiten subcategorizar, al igual que los subcomponentes) no permiten múltiples componentes de ámbito, lo que también significa que sus módulos no estarán codificados. Esto se debe a la forma en que no puede heredar desde múltiples ámbitos, al igual que no puede heredar de múltiples clases en Java.
Los proveedores sin cobertura hacen que el módulo no conserve una sola instancia sino una nueva en cada llamada inyectada. Para obtener dependencias con ámbito, debe proporcionar el alcance en los métodos del proveedor del módulo también.
@Module
public class InteractorModule {
@Provides
@Singleton
public LeftNavigationDrawerInteractor leftNavigationDrawerInteractor() {
return new LeftNavigationDrawerInteractorImpl();
}
...
}
En una aplicación, si usa componentes Singleton en todas partes, no necesitará más componentes, a menos que cree subcopios. Si lo desea, incluso puede considerar hacer de sus módulos un proveedor de datos completo para sus vistas y presentadores.
@Component(dependencies = {ApplicationComponent.class}, modules = {DetailActivityModule.class})
@ActivityScope
public interface DetailActivityComponent extends ApplicationComponent {
DataObject data();
void inject(DetailActivity detailActivity);
}
@Module
public class DetailActivityModule {
private String parameter;
public DetailActivityModule(String parameter) {
this.parameter = parameter;
}
@Provides
public DataObject data(RealmHolder realmHolder) {
Realm realm = realmHolder.getRealm();
return realm.where(DataObject.class).equalTo("parameter", parameter).findFirst();
}
}
El subescaneo le permite tener múltiples instancias de su presentador, que luego pueden almacenar el estado. Esto tiene sentido, por ejemplo, Mortar / Flow , donde cada pantalla tiene su propia "ruta", y cada ruta tiene su propio componente , para proporcionar los datos como un "blueprint".
public class FirstPath
extends BasePath {
public static final String TAG = " FirstPath";
public final int parameter;
public FirstPath(int parameter) {
this.parameter = parameter;
}
//...
@Override
public int getLayout() {
return R.layout.path_first;
}
@Override
public FirstViewComponent createComponent() {
FirstPath.FirstViewComponent firstViewComponent = DaggerFirstPath_FirstViewComponent.builder()
.applicationComponent(InjectorService.obtain())
.firstViewModule(new FirstPath.FirstViewModule(parameter))
.build();
return firstViewComponent;
}
@Override
public String getScopeName() {
return TAG + "_" + parameter;
}
@ViewScope //needed
@Component(dependencies = {ApplicationComponent.class}, modules = {FirstViewModule.class})
public interface FirstViewComponent
extends ApplicationComponent {
String data();
FirstViewPresenter firstViewPresenter();
void inject(FirstView firstView);
void inject(FirstViewPresenter firstViewPresenter);
}
@Module
public static class FirstViewModule {
private int parameter;
public FirstViewModule(int parameter) {
this.parameter = parameter;
}
@Provides
public String data(Context context) {
return context.getString(parameter);
}
@Provides
@ViewScope //needed to preserve scope
public FirstViewPresenter firstViewPresenter() {
return new FirstViewPresenter();
}
}
public static class FirstViewPresenter
extends ViewPresenter<FirstView> {
public static final String TAG = FirstViewPresenter.class.getSimpleName();
@Inject
String data;
public FirstViewPresenter() {
Log.d(TAG, "First View Presenter created: " + toString());
}
@Override
protected void onEnterScope(MortarScope scope) {
super.onEnterScope(scope);
FirstViewComponent firstViewComponent = scope.getService(DaggerService.TAG);
firstViewComponent.inject(this);
Log.d(TAG, "Data [" + data + "] and other objects injected to first presenter.");
}
@Override
protected void onSave(Bundle outState) {
super.onSave(outState);
FirstView firstView = getView();
outState.putString("input", firstView.getInput());
}
@Override
protected void onLoad(Bundle savedInstanceState) {
super.onLoad(savedInstanceState);
if(!hasView()) {
return;
}
FirstView firstView = getView();
if(savedInstanceState != null) { //needed check
firstView.setInput(savedInstanceState.getString("input"));
}
}
public void goToNextActivity() {
FirstPath firstPath = Path.get(getView().getContext());
if(firstPath.parameter != R.string.hello_world) {
Flow.get(getView()).set(new FirstPath(R.string.hello_world));
} else {
Flow.get(getView()).set(new SecondPath());
}
}
}
}