Search code examples
androiddependency-injectionandroid-recyclerviewdagger-2android-mvp

Injecting RecyclerView.Adapter using Dagger


I'm trying to inject a RecyclerView adapter into an Activity, but i'm not getting the result I'm expecting. The list is not being filled.

This is my adapter implementation:

public class ListAdapter extends RecyclerView.Adapter<ListAdapter.ListItemViewHolder> {

private TopMoviesActivityMVP.Presenter presenter;

public ListAdapter(TopMoviesActivityMVP.Presenter presenter) {
    this.presenter = presenter;
}

@NonNull
@Override
public ListAdapter.ListItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_movie_list, parent, false);
    return new ListItemViewHolder(view);
}

@Override
public void onBindViewHolder(@NonNull ListAdapter.ListItemViewHolder holder, int position) {
    presenter.bindRowViewAtPosition(holder, position);
}

@Override
public int getItemCount() {
    return presenter.getRowsCount();
}

public static class ListItemViewHolder extends RecyclerView.ViewHolder implements TopMoviesRowView {

    @BindView(R.id.tv_task_name)
    TextView tvName;
    @BindView(R.id.tv_task_country)
    TextView tvCountry;

    public ListItemViewHolder(View itemView) {
        super(itemView);
        ButterKnife.bind(this, itemView);
    }

    @Override
    public void setName(String name) {
        tvName.setText(name);
    }

    @Override
    public void setCountry(String country) {
        tvCountry.setText(country);
    }

}}

As you can see the adapter receives an instance of the presenter, which is being used to get access to the downloaded data. Inside my activity I'm trying to inject the adapter:

public class TopMoviesActivity extends AppCompatActivity implements TopMoviesActivityMVP.View {

@BindView(R.id.list_activity_rootview)
ViewGroup rootView;
@BindView(R.id.recycler_view)
RecyclerView recyclerView;

@Inject
TopMoviesActivityMVP.Presenter presenter;
@Inject
RecyclerView.Adapter<ListAdapter.ListItemViewHolder> listAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_top_movies);

    ((App)getApplication()).getComponent().inject(this);

    ButterKnife.bind(this);
}

@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
    super.onPostCreate(savedInstanceState);
    recyclerView.setAdapter(listAdapter);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
}

@Override
protected void onResume() {
    super.onResume();
    presenter.setView(this);
    presenter.loadData();
}

@Override
protected void onDestroy() {
    super.onDestroy();
    presenter.rxUnsubscribe();
}

@Override
public void updateData() {
    int count = presenter.getRowsCount();
    if (count == 0) {
        listAdapter.notifyItemInserted(0);
    } else {
        listAdapter.notifyItemInserted(count-1);
    }
}

@Override
public void showSnackbar(String msg) {
    Snackbar.make(rootView, msg, Snackbar.LENGTH_SHORT).show();
}}

Even when listAdapter.notifyItemInserted is being called after the downloaded data is available, onCreateViewHolder is not being called.

If I don't inject the adapter, everything works as expected:

@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
    super.onPostCreate(savedInstanceState);
    listAdapter = new ListAdapter(presenter);
    recyclerView.setAdapter(listAdapter);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
}

This is my module:

@Module
public class TopMoviesModule {

@Provides
public RecyclerView.Adapter<ListAdapter.ListItemViewHolder> providesAdapter(TopMoviesActivityMVP.Presenter presenter) {
    return new ListAdapter(presenter);
}

@Provides
public TopMoviesActivityMVP.Presenter providesTopMoviesPresenter(TopMoviesActivityMVP.Model model) {
    return new TopMoviesPresenter(model);
}

@Provides
public TopMoviesActivityMVP.Model providesTopMoviesModel(Repository repository) {
    return new TopMoviesModel(repository);
}

@Provides
@Singleton
public Repository providesRepository(MoviesApiService moviesApiService, CountryApiService countryApiService) {
    return new TopMoviesRepository(moviesApiService, countryApiService);
}}

I can't figure out what I'm missing.


Solution

  • I think its because of the scoping, the thing is that since presenter doesn't have a scope attached to it you're getting 2 different instances of the presenter in the Activity and the adapter. You can confirm that by adding a break-point to your code.

    Also it would be very wrong to have a singleton scoped presenter that gets tied up with the Application scope.

    I would recommend using a subcomponent for your Activity with a custom scope so it doesn't outlive the activity and you can have the adapter and presenter scoped to that Activity.

    For more info on Dagger Subcomponents check this link