Comprensión de los alcances en Daga 2

Tengo un error relacionado con el alcance en Daga 2 y estoy tratando de entender cómo puedo solucionarlo.

Tengo una CompaniesActivity que muestra a las empresas. Cuando el usuario selecciona un elemento, los empleados de la empresa seleccionada se muestran en EmployeesActivity . Cuando el usuario selecciona a un empleado, su detalle se muestra en EmployeeDetailActivity .

 class Company { List<Employee> employees; } 

Clase CompaniesViewModel contiene las empresas y el seleccionado (o null ):

 class CompaniesViewModel { List<Company> companies; Company selected; } 

CompaniesActivity tiene una referencia a CompaniesViewModel :

 class CompaniesActivity extends Activity { @Inject CompaniesViewModel viewModel; @Override protected void onCreate(Bundle b) { //more stuff getComponent().inject(this); showCompanies(viewModel.companies); } //more stuff private onCompanySelected(Company company) { viewModel.selected = company; startActivity(new Intent(this, EmployeesActivity.class)); } } 

Clase EmployeesViewModel contiene los empleados y el seleccionado (o null ):

 class EmployeesViewModel { List<Employee> employees; Employee selected; } 

EmployeesActivity tiene una referencia a EmployeesViewModel :

  class EmployeesActivity extends Activity { @Inject EmployeesViewModel viewModel; @Override protected void onCreate(Bundle b) { //more stuff getComponent().inject(this); showEmployees(viewModel.employees); } //more stuff private onEmployeeSelected(Employee emp) { viewModel.selected = emp; startActivity(new Intent(this, EmployeeDetailActivity.class)); } } 

Por último, en EmployeeDetailActivity , obtengo Employee seleccionado desde el modelo de vista y mostrar su detalle:

  class EmployeeDetailActivity extends Activity { @Inject EmployeesViewModel viewModel; @Override protected void onCreate(Bundle b) { //more stuff getComponent().inject(this); showEmployeeDetail(viewModel.selected); // NullPointerException } } 

Tengo NullPointerException porque la instancia de EmployeesActivity en EmployeesActivity no es la misma que la EmployeeDetailActivity y, en la segunda, viewModel.selected es null .

Este es mi módulo de daga:

 @Module class MainModule { @Provides @Singleton public CompaniesViewModel providesCompaniesViewModel() { CompaniesViewModel cvm = new CompaniesViewModel(); cvm.companies = getCompanies(); return cvm; } @Provides public EmployeesViewModel providesEmployeesViewModel(CompaniesViewModel cvm) { EmployeesViewModel evm = new EmployeesViewModel(); evm.employees = cvm.selected.employees; return evm; } } 

Tenga en cuenta que CompaniesViewModel es singleton ( @Singleton ) pero EmployeesViewModel no lo es, ya que tiene que ser recreado cada vez que el usuario selecciona una empresa (la lista de empleados contendrá otros elementos).

Podría set los empleados de la empresa a EmployeesViewModel cada vez que el usuario selecciona una empresa, en lugar de crear una nueva instancia. Pero me gustaría que CompaniesViewModel sea ​​inmutable.

¿Como puedo resolver esto? Cualquier consejo será apreciado.

Desafortunadamente, creo que abusas del marco de DI en este caso, y los problemas que encuentras son "olores de código" – estos problemas sugieren que estás haciendo algo mal.

Los marcos de DI deben utilizarse para inyectar dependencias críticas (objetos colaboradores) en componentes de nivel superior, y la lógica que realiza estas inyecciones debe ser totalmente independiente de la lógica de negocio de su aplicación.

Desde la primera vista todo se ve bien – se utiliza Dagger con el fin de inyectar CompaniesViewModel y EmployeesViewModel en la Activity . Esto podría haber estado bien (aunque no lo haría de esta manera) si estos eran reales "objetos". Sin embargo, en su caso, se trata de "Estructuras de datos" (por lo tanto, desea que sean inmutables).

Esta distinción entre objetos y estructuras de datos no es trivial, pero muy importante. Esta entrada de blog lo resume bastante bien.

Ahora, si intenta inyectar estructuras de datos con el marco de DI, en última instancia, convertir el marco en "proveedor de datos" de la aplicación, delegando parte de la funcionalidad de negocio en él. Por ejemplo: parece que EmployeesViewModel es independiente de CompaniesViewModel , pero es una "mentira": el código en el método @Provides une de forma lógica, ocultando así la dependencia. Una buena "regla general" en este contexto es que si el código DI depende de los detalles de implementación de los objetos inyectados (por ejemplo, métodos de llamada, campos de acceso, etc.), suele ser una indicación de una separación insuficiente de las preocupaciones.

Dos recomendaciones específicas:

  1. No mezcle la lógica de negocio con la lógica DI. En su caso – no inyecte estructuras de datos, sino que inyecte objetos que proporcionan acceso a los datos (malo) o exponen la funcionalidad requerida mientras se extraen los datos (mejor).
  2. Creo que su intento de compartir un modelo de vista entre varias pantallas no es un diseño muy robusto. Sería mejor tener una instancia separada de View-Model para cada pantalla. Si necesitas "compartir" el estado entre las pantallas, entonces, dependiendo de los requisitos específicos, puedes hacer esto con 1) Intent extras 2) Objeto global 3) Prefs compartidos 4) SQLite

De acuerdo con este artículo acerca de Custom Scopes:

http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/

En corto – los ámbitos nos dan "singletons locales" que viven mientras alcance sí mismo.

Sólo para ser claros – no hay @ActivityScope o @ApplicationScope anotaciones proporcionadas por defecto en Dagger 2. Es sólo el uso más común de ámbitos personalizados. Sólo el alcance @Singleton está disponible de forma predeterminada (proporcionado por el propio Java), y el punto está utilizando un ámbito no es suficiente (!) Y usted tiene que cuidar el componente que contiene ese ámbito. Esto significa mantener una referencia a ella dentro de la clase de aplicación y reutilizarla cuando cambie la actividad.

 public class GithubClientApplication extends Application { private AppComponent appComponent; private UserComponent userComponent; //... public UserComponent createUserComponent(User user) { userComponent = appComponent.plus(new UserModule(user)); return userComponent; } public void releaseUserComponent() { userComponent = null; } //... } 

Puedes echar un vistazo a este ejemplo de proyecto:

http://github.com/mmirhoseini/marvel

Y este artículo:

https://hackernoon.com/yet-another-mvp-article-part-1-lets-get-to-know-the-project-d3fd553b3e21

Para familiarizarse con MVP y aprender cómo funciona el alcance de la daga.

Hay un par de temas aquí y que sólo están relacionados oblicuamente con los ámbitos Dagger 2.

En primer lugar, el hecho de que haya utilizado el término "ViewModel" sugiere que está intentando utilizar la arquitectura MVVM . Una de las características más destacadas de MVVM es la separación de capas. Sin embargo, su código no ha logrado ninguna separación entre modelo y vista-modelo.

Echemos un vistazo a esta definición de modelo de Eric Evans:

Un modelo de dominio es un sistema de abstracciones que describe aspectos seleccionados de una esfera de conocimiento, influencia o actividad (un dominio). 2

Aquí su esfera de conocimiento es una empresa y sus empleados. Mirando su EmployeesViewModel , contiene al menos un campo que es probablemente mejor aislado en la capa del modelo.

 class EmployeesViewModel { List<Employee> employees; //model layer Employee selected; } 

Tal vez sea simplemente una elección desafortunada de nombre, pero creo que su intención es crear modelos de vista adecuados para que cualquier respuesta a esta pregunta debería abordar eso. Mientras que la selección está asociada con la vista, la clase no califica realmente como una abstracción de la vista. Un verdadero modelo de vista probablemente de alguna manera coincida con la forma en que el empleado se muestra en la pantalla. Digamos que usted tiene "nombre" y "fecha de nacimiento" TextViews. Entonces, un modelo de vista expondría los métodos que proporcionan el texto, la visibilidad, el color, etc. para esas TextViews.

En segundo lugar, lo que usted está proponiendo es usar (singleton) Dagger 2 alcances para comunicarse entre Actividades. Desea que la empresa seleccionada en CompaniesActivity se comunique a la EmployeesActivity y al empleado seleccionado en EmployeesActivity para comunicarse con la EmployeeDetailActivity . Ustedes están preguntando cómo lograr esto haciendo que todos ellos usen el mismo objeto global compartido.

Si bien esto puede ser técnicamente posible utilizando Dagger 2, el enfoque correcto en Android para comunicarse entre las actividades es utilizar intentos, en lugar de objetos compartidos. Las respuestas a esta pregunta son una muy buena explicación de este punto.

Aquí está una solución propuesta: No está claro lo que está haciendo para obtener realmente la List<Company> . Tal vez usted está recibiendo de un db, tal vez usted está recibiendo de una solicitud en la web en caché. Sea lo que sea, encapsule esto en un objeto CompaniesRepository . Del mismo modo para EmployeesRepository .

Así que usted tendrá algo como:

 public abstract class EmployeesRepository { List<Employee> getAll(); Employee get(int id); int getId(Employee employee); } 

Haga algo similar para una clase de CompaniesRepository . Estas dos clases de recuperación pueden ser singletons y ser inicializadas en su módulo.

 @Module class MainModule { @Provides @Singleton public CompaniesRepository(Dependency1 dependency1) { //TODO: code you need to generate the companies retrieval object } @Provides @Singleton public EmployeesRepository(Dependency2 dependency2) { //TODO: code you need to generate the employees retrieval object } } 

Su EmployeesActivity se parece ahora a algo así:

 class EmployeesActivity extends Activity { @Inject CompaniesRepository companiesRepository; @Inject EmployeesRepository employeesRepository; @Override protected void onCreate(Bundle b) { //more stuff getComponent().inject(this); //retrieve the id of the company selected in the previous activity //and use that to get the company model int selectedCompanyId = b.getIntExtra(BUNDLE_COMPANY_ID, -1); //TODO: handle case where no company id has been passed into the activity Company selectedCompany = companiesRepository.get(selectedCompanyId); showEmployees(selectedCompany.getEmployees); } //more stuff private onEmployeeSelected(Employee emp) { int selectedEmployeeId = employeesRepository.getId(emp); Intent employeeDetail = new Intent(); employeeDetail.putExtra(BUNDLE_EMPLOYEE_ID, selectedEmployeeId); startActivity(employeeDetail)); } } 

Amplíe este ejemplo a sus otras dos actividades y se acercará a la arquitectura estándar de una aplicación de Android y utilizará Dagger 2 sin mezclar la capa de modelo, la capa de vista, etc.

  • Inyección de múltiples componentes independientes
  • Dagger2 error: No se puede proporcionar sin el constructor @Inject
  • Dagger 2 dependencias de componentes
  • Daga 2 Singleton Componente Depend en Singleton
  • Método de inyección con Dagger 2
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.