如何使用dagger2将Activity注入Adapter

8

Android Studio 3.0 金丝雀版 8

我试图将我的 MainActivity 注入到我的适配器中。然而,我的解决方案可以正常工作,但我认为这是一种代码异味并不是正确的做法。

我的适配器片段看起来像这样,但我不喜欢的是我必须将 Activity 强制转换为 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;
    }
}

在我的模块中,我在构造函数中传递了Activity,并将其传递给了Adapter。

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

在我的应用程序类中,我创建了组件,并且使用了一个子组件作为适配器。在这里,我必须传递Activity,但我不确定这是否是一个好主意。
@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));
}

我是这样注入我的Fragment的:

@Inject RecipeAdapter recipeAdapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ((BusbyBakingApplication)getActivity().getApplication())
                .createRecipeListComponent(getActivity())
                .inject(this);
    }

尽管上述设计是可行的,但我认为它存在代码气味问题,因为我必须将 Activity 强制转换为 MainActivity。之所以使用 Activity,是因为我想使这个模块更加通用。不知道是否有更好的方法?
=============== 使用接口进行更新
接口
public interface RecipeItemClickListener {
    void onRecipeItemClick(Recipe recipe);
}

实现

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

在我的模块中,我有以下提供者。
@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);
    }
}

然后我通过构造函数注入在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;
    }
}

我有一个问题,RecipeAdapter中的构造函数是否需要使用@Inject注解?因为它可以在有或没有@Inject的情况下正常工作。


请查看构造函数注入及其使用方法。创建仅调用构造函数的“provides*”方法只是噪音和需要维护的代码。 - David Medenjak
3个回答

14

不要在适配器中传递活动(Activity) - 这是一个非常糟糕的做法。

只注入你关心的字段。

在您的示例中:将接口传递到适配器中以跟踪项目点击。


mrsegev使用您在答案中指定的接口。我已更新我的问题,并使用接口提供了解决方案。只是想知道构造函数是否需要@Inject注释? - ant2009
我希望我理解了你的问题 - 如果你想要注入依赖项,请使用 @Inject 注释。 - Yossi Segev
没有 @Inject 注解,Dagger 将不会使用对象图来满足依赖关系。 - Yossi Segev

8
如果你需要一个MainActivity,那么你也应该提供它。而不是声明Activity,请为你的模块声明MainActivity
@Module
public class RecipeListModule {
  private MainActivity activity;

  public RecipeListModule(MainActivity activity) {
    this.activity = activity;
  }
}

你的适配器应该只是请求它(对于 Android框架类型使用构造函数注入!)

@RecipeListScope
class RecipeAdapter {

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

}

如果您的模块想要使用Activity而不是MainActivity,那么您需要像先前提到的那样声明一个接口。然后,您的适配器将声明该接口作为其依赖项。
但在某些模块中,您仍然需要将该接口绑定到您的MainActivity上,并且其中一个模块需要知道如何提供该依赖项。
// in some abstract module
@Binds MyAdapterInterface(MainActivity activity) // bind the activity to the interface

回答问题的更新部分

只有一个问题,RecipeAdapter中的构造函数是否需要@Inject注释。因为没有使用@Inject也可以正常工作。

它不需要,因为你仍然没有使用构造函数注入。你仍然在providesRecipeAdapter()中自己调用构造函数。作为一条经验法则——如果你想正确使用Dagger,请不要自己调用new。如果你想使用new,请问问自己是否可以使用构造函数注入。

你展示的同一个模块可以编写为以下形式,利用@Binds将实现绑定到接口,并实际使用构造函数注入来创建适配器(这就是为什么我们不必为其编写任何方法!代码量更少,错误更少,类更易读)

正如你所看到的,我不需要自己使用new——Dagger会为我创建对象。

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

2

个人建议采用以下技巧

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

谢谢,但我认为这更像是一种hack,可能不可测试。 - ant2009
1
您还可以传递接口。 - EpicPandaForce
我已经更新了我的问题,使用接口来代替。如果您认为有任何问题,请查看一下。 - ant2009

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接