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.
- Alcance de la actividad Dagger2, ¿cuántos módulos / componentes necesito?
- No se puede encontrar la clase de símbolo "Generated" para Dagger 2
- ¿Cómo anula un módulo / dependencia en una prueba de unidad con Dagger 2.0?
- Mejor Dagger dependencia inyección basada en tipo de construcción y sabor?
- Dagger 2, módulos de biblioteca y @Singleton
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.
- ¿Puedo extender una aplicación personalizada en Espresso?
- Java.lang.NoClassDefFound usando Dagger 2 para Android 4
- Dagger2: Error cuando dos componentes tienen la misma firma de método de inyección
- Dagger ejemplo construido a través de eclipse falla con 'Asegúrese de que la generación de código se ejecutó para este módulo.'
- Dagger 2: Inyectar el parámetro introducido por el usuario en el objeto
- Dagger código que da NoClassDefFoundError en las pruebas de instrumentación de Android, pero funciona en la aplicación normal
- Dagger 2.2 método del módulo de constructor de componentes obsoleto
- Utilice los módulos Dagger sin la directiva "inyecta"
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; } }