support que ejemplo activitykey android dependency-injection dagger-2

android - que - Entendiendo alcances en Dagger 2



dagger kotlin (3)

Hay un par de problemas aquí y que están relacionados oblicuamente con los alcances de Dagger 2.

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

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. Al mirar su EmployeesViewModel , contiene al menos un campo que probablemente esté mejor aislado en la capa del modelo.

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

Tal vez sea simplemente una desafortunada elección de nombre, pero creo que su intención es crear modelos de visualización adecuados, por lo que cualquier respuesta a esta pregunta debería abordar eso. Si bien la selección está asociada a la vista, la clase realmente no califica como una abstracción de la vista. Un modelo de vista real probablemente coincidiría de alguna manera con la forma en que el Empleado se muestra en la pantalla. Supongamos que tiene TextViews de "nombre" y "fecha de nacimiento". Luego, un modelo de vista expondría los métodos que proporcionan el texto, la visibilidad, el color, etc. para esos TextViews.

En segundo lugar, lo que está proponiendo es utilizar (unísono) Dagger 2 ámbitos para comunicarse entre Actividades. Desea que la empresa seleccionada en CompaniesActivity se comunique a EmployeesActivity y que el empleado seleccionado en EmployeesActivity se comunique a EmployeeDetailActivity . Usted está preguntando cómo lograr esto al hacer que todos usen el mismo objeto global compartido.

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

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

Entonces tendrás algo como:

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

Haz algo similar para una clase de CompaniesRepository . Estas dos clases de recuperación pueden ser simples y se inicializan 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 } }

Your EmployeesActivity ahora se ve más o menos 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)); } }

Extienda este ejemplo a sus otras dos actividades y se acercará a la arquitectura estándar para una aplicación de Android y usará Dagger 2 sin mezclar su capa de modelo, capa de vista, etc.

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

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

class Company { List<Employee> employees; }

Class CompaniesViewModel contiene las compañías 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)); } }

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

Finalmente, en EmployeeDetailActivity , selecciono Employee from view model y le muestro sus detalles:

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

Obtengo NullPointerException porque la instancia EmployeesViewModel en EmployeesActivity no es lo mismo que EmployeeDetailActivity y, en la segunda, viewModel.selected es null .

Este es mi módulo 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, porque tiene que volver a crearse cada vez que el usuario selecciona una empresa (la lista de empleados contendrá otros elementos).

Podría set los empleados de la empresa en 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.


De acuerdo con este artículo sobre ámbitos personalizados:

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

En resumen, los alcances nos dan "singletons locales" que viven tanto como el alcance mismo.

Para que quede claro, no hay anotaciones de @ActivityScope o @ApplicationScope proporcionadas por defecto en Dagger 2. Es el uso más común de los ámbitos personalizados. Solo el alcance de @Singleton está disponible de manera predeterminada (proporcionado por Java), y el punto es que usar un alcance no es suficiente (!) Y debe cuidar el componente que contiene ese alcance. Esto significa mantener una referencia dentro de la clase de Aplicación y reutilizarla cuando la Actividad cambie.

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; } //... }

Puede echar un vistazo a este proyecto de muestra:

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

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


Lamentablemente, creo que en este caso se abusa del marco de DI, y los problemas que encuentra son "olores de código": estos problemas indican que está haciendo algo mal.

Los marcos DI deben usarse 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 comercial de su aplicación.

Desde el primer vistazo, todo se ve bien: utiliza Dagger para inyectar CompaniesViewModel y EmployeesViewModel en Activity . Esto podría haber estado bien (aunque no lo haría de esta manera) si se tratara de "Objetos" reales. Sin embargo, en su caso, estas son "estructuras de datos" (por lo tanto, desea que sean inmutables).

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

Ahora, si intenta inyectar Data Structures usando DI framework, finalmente convertirá el framework en "proveedor de datos" de la aplicación, delegando así parte de la funcionalidad empresarial. Por ejemplo: parece que EmployeesViewModel es independiente de CompaniesViewModel , pero es una "mentira": el código en el método @Provides vincula lógicamente, "ocultando" 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.), generalmente es una indicación de una separación insuficiente de las preocupaciones.

Dos recomendaciones específicas:

  1. No mezcle la lógica comercial con la lógica DI. En su caso, no inyecte estructuras de datos, sino inyecte objetos que proporcionen acceso a los datos (incorrectos) o exponga la funcionalidad requerida al tiempo que abstrae los datos (mejor).
  2. Creo que su intento de compartir un View-Model entre varias pantallas no es un diseño muy robusto. Sería mejor tener una instancia separada de View-Model para cada pantalla. Si necesita "compartir" estado entre pantallas, entonces, dependiendo de los requisitos específicos, puede hacerlo con 1) extras de intención 2) objeto global 3) preferencias compartidas 4) SQLite