Cómo inyectar una actividad en un adaptador usando dagger2

Android Studio 3.0 Canary 8

Estoy tratando de inyectar mi MainActivity en mi adaptador. Sin embargo, mi solución funciona bien, pero creo que es un olor de código y no la forma correcta de hacerlo.

Mi fragmento de adaptador se parece a esto, pero no me gusta de esto es que tengo que echar la Activity a MainActivity :

 public class RecipeAdapter extends RecyclerView.Adapter<RecipeListViewHolder> { private List<Recipe> recipeList = Collections.emptyList(); private Map<Integer, RecipeListViewHolderFactory> viewHolderFactories; private MainActivity mainActivity; public RecipeAdapter(Activity activity, Map<Integer, RecipeListViewHolderFactory> viewHolderFactories) { this.recipeList = new ArrayList<>(); this.viewHolderFactories = viewHolderFactories; this.mainActivity = (MainActivity)activity; } @Override public RecipeListViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { /* Inject the viewholder */ final RecipeListViewHolder recipeListViewHolder = viewHolderFactories.get(Constants.RECIPE_LIST).createViewHolder(viewGroup); recipeListViewHolder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { /* Using the MainActivity to call a callback listener */ mainActivity.onRecipeItemClick(getRecipe(recipeListViewHolder.getAdapterPosition())); } }); return recipeListViewHolder; } } 

En mi módulo, paso la actividad en el constructor del módulo y lo paso al adaptador.

 @Module public class RecipeListModule { private Activity activity; public RecipeListModule() {} public RecipeListModule(Activity activity) { this.activity = activity; } @RecipeListScope @Provides RecipeAdapter providesRecipeAdapter(Map<Integer, RecipeListViewHolderFactory> viewHolderFactories) { return new RecipeAdapter(activity, viewHolderFactories); } } 

En mi clase de aplicación, creo los componentes y estoy usando un subcomponente para el adaptador. Aquí tengo que pasar la actividad que no estoy seguro es una buena idea.

 @Override public void onCreate() { super.onCreate(); applicationComponent = createApplicationComponent(); recipeListComponent = createRecipeListComponent(); } public BusbyBakingComponent createApplicationComponent() { return DaggerBusbyBakingComponent.builder() .networkModule(new NetworkModule()) .androidModule(new AndroidModule(BusbyBakingApplication.this)) .exoPlayerModule(new ExoPlayerModule()) .build(); } public RecipeListComponent createRecipeListComponent(Activity activity) { return recipeListComponent = applicationComponent.add(new RecipeListModule(activity)); } 

Mi Fragmento Me inyecto así:

 @Inject RecipeAdapter recipeAdapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ((BusbyBakingApplication)getActivity().getApplication()) .createRecipeListComponent(getActivity()) .inject(this); } 

A pesar de que el diseño anterior funciona, creo que es un olor de código, ya que tengo que echar la actividad a MainActivity. La razón por la que uso la Actividad como quiero hacer este módulo más genérico.

Sólo me pregunto si hay una mejor manera

=============== UPDATE USING INTERFACE

Interfaz

 public interface RecipeItemClickListener { void onRecipeItemClick(Recipe recipe); } 

Implementación

 public class RecipeItemClickListenerImp implements RecipeItemClickListener { @Override public void onRecipeItemClick(Recipe recipe, Context context) { final Intent intent = Henson.with(context) .gotoRecipeDetailActivity() .recipe(recipe) .build(); context.startActivity(intent); } } 

En mi módulo, tengo los siguientes proveedores

 @Module public class RecipeListModule { @RecipeListScope @Provides RecipeItemClickListener providesRecipeItemClickListenerImp() { return new RecipeItemClickListenerImp(); } @RecipeListScope @Provides RecipeAdapter providesRecipeAdapter(RecipeItemClickListener recipeItemClickListener, Map<Integer, RecipeListViewHolderFactory> viewHolderFactories) { return new RecipeAdapter(recipeItemClickListener, viewHolderFactories); } } 

Luego lo uso a través de inyección de constructor en el RecipeAdapter

 public class RecipeAdapter extends RecyclerView.Adapter<RecipeListViewHolder> { private List<Recipe> recipeList = Collections.emptyList(); private Map<Integer, RecipeListViewHolderFactory> viewHolderFactories; private RecipeItemClickListener recipeItemClickListener; @Inject /* IS THIS NESSESSARY - AS IT WORKS WITH AND WITHOUT THE @Inject annotation */ public RecipeAdapter(RecipeItemClickListener recipeItemClickListener, Map<Integer, RecipeListViewHolderFactory> viewHolderFactories) { this.recipeList = new ArrayList<>(); this.viewHolderFactories = viewHolderFactories; this.recipeItemClickListener = recipeItemClickListener; } @Override public RecipeListViewHolder onCreateViewHolder(final ViewGroup viewGroup, int i) { /* Inject the viewholder */ final RecipeListViewHolder recipeListViewHolder = viewHolderFactories.get(Constants.RECIPE_LIST).createViewHolder(viewGroup); recipeListViewHolder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { recipeItemClickListener.onRecipeItemClick(getRecipe(recipeListViewHolder.getAdapterPosition()), viewGroup.getContext()); } }); return recipeListViewHolder; } } 

Sólo una pregunta, es la necesidad de anotación @Inject para el constructor en el RecipeAdapter. Como funciona con o sin el @Inject.

Si necesita una MainActivity , también debe proporcionarla. En lugar de Activity declarar MainActivity para su módulo.

 @Module public class RecipeListModule { private MainActivity activity; public RecipeListModule(MainActivity activity) { this.activity = activity; } } 

Y su adaptador sólo debe solicitarlo (Inyección Constructor para no Android Framework tipos!)

 @RecipeListScope class RecipeAdapter { @Inject RecipeAdapter(MainActivity activity, Map<Integer, RecipeListViewHolderFactory> viewHolderFactories) { // ... } } 

Si desea que su módulo utilice Activity y no MainActivity , tendrá que declarar una interfaz como ya se mencionó. El adaptador declararía entonces la interfaz como su dependencia.

Pero en algunos módulos todavía tendrá que vincular esa interfaz a su MainActivity y un módulo necesita saber cómo proporcionar la dependencia.

 // in some abstract module @Binds MyAdapterInterface(MainActivity activity) // bind the activity to the interface 

Abordar la parte actualizada de la pregunta

Sólo una pregunta, es la @Inject anotación @Inject para el constructor en el RecipeAdapter. Como funciona con o sin el @Inject .

Funciona sin él porque todavía no está usando la inyección del constructor. Todavía estás llamando al constructor en providesRecipeAdapter() . Como regla general – si quieres usar Dagger correctamente – no llames nunca a ti mismo. Si desea utilizar new pregúntese si podría utilizar la inyección de constructor en su lugar.

El mismo módulo que muestra podría escribirse de la siguiente manera, haciendo uso de @Binds para enlazar una implementación a la interfaz, y de hecho usando la inyección del constructor para crear el adaptador (¡es por eso que no tenemos que escribir ningún método para ello! Código para mantener, menos errores, clases más legibles)

Como ves, no necesito usarme a mí mismo. Dagger creará los objetos para mí.

 public abstract class RecipeListModule { @RecipeListScope @Binds RecipeItemClickListener providesRecipeClickListener(RecipeItemClickListenerImp listener); } 

No pasar las actividades en los adaptadores – Esta es una práctica realmente mala.

Inyecte sólo los campos que le interesan.

En el ejemplo: Pase una interfaz en el adaptador para realizar un seguimiento del elemento.

Personalmente haría el siguiente truco

 public class MainActivity extends AppCompatActivity { private static final String TAG = "__ACTIVITY__"; public static MainActivity get(Context context) { // noinspection ResourceType return (MainActivity)context.getSystemService(TAG); } @Override protected Object getSystemService(String name) { if(TAG.equals(name)) { return this; } return super.getSystemService(name); } } public class RecipeAdapter extends RecyclerView.Adapter<RecipeListViewHolder> { private List<Recipe> recipeList = Collections.emptyList(); private Map<Integer, RecipeListViewHolderFactory> viewHolderFactories; public RecipeAdapter(Map<Integer, RecipeListViewHolderFactory> viewHolderFactories) { this.recipeList = new ArrayList<>(); this.viewHolderFactories = viewHolderFactories; } @Override public RecipeListViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { /* Inject the viewholder */ final RecipeListViewHolder recipeListViewHolder = viewHolderFactories.get(Constants.RECIPE_LIST).createViewHolder(viewGroup); recipeListViewHolder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { MainActivity mainActivity = MainActivity.get(v.getContext()); if(recipeListViewHolder.getAdapterPosition() != -1) { mainActivity.onRecipeItemClick( getRecipe(recipeListViewHolder.getAdapterPosition())); } } }); return recipeListViewHolder; } } 
  • Escopetas en Dagger 2
  • Dagger 2 inyecta error en AppCompatActivity
  • Daga que no genera componentes para / clase de prueba
  • Dagger 2 "Dagger" prefijo componente no es capaz de compilar? Clase generada automáticamente
  • Adición de clases sin actividad a Dagger 2 Graph Android
  • No se puede proporcionar sin un constructor @Inject o de un método @ Proporcionado-anotado
  • Dagger 2: Proporcionar la misma instancia entre múltiples componentes con el mismo alcance en diferentes módulos de biblioteca
  • ¿Cuándo usar Dagger en la aplicación Android?
  • Android Espresso con Dagger
  • Componente Dagger2 con más de una dependencia
  • Inyectar base de datos en un ContentProvider con daga
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.