Implement onItemClickListener using MVP pattern
Asked Answered
N

2

11

I am learning MVP and got confused where and how should I implement onClickListener while not ruining mvp concept here.

Followed this guide: https://android.jlelse.eu/recyclerview-in-mvp-passive-views-approach-8dd74633158

My implementation.

Adapter:

public class RepositoriesRecyclerAdapter extends RecyclerView.Adapter<RepositoriesRecyclerAdapter.RepoViewHolder> {


private final RepositoriesListPresenter presenter;

public RepositoriesRecyclerAdapter(RepositoriesListPresenter repositoriesPresenter) {
    this.presenter = repositoriesPresenter;
}

@Override
public RepositoryViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    return new RepositoryViewHolder(LayoutInflater.from(parent.getContext())
                                            .inflate(R.layout.cell_repo_view, parent, false));
}

@Override
public void onBindViewHolder(RepositoryViewHolder holder, int position) {
    presenter.onBindRepositoryRowViewAtPosition(position, holder);

}

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

}

RepositoryViewHolder's

public class RepositoryViewHolder extends RecyclerView.ViewHolder implements RepositoryRowView {

    TextView titleTextView;
    TextView starsCountTextView;

    public RepositoryViewHolder(View itemView) {
        super(itemView);
        titleTextView = itemView.findViewById(R.id.repoTitleText);
        starsCountTextView = itemView.findViewById(R.id.repoStarsCountText);
    }

    @Override
    public void setTitle(String title) {
        titleTextView.setText(title);
    }

    @Override
    public void setStarCount(int starCount) {
        starsCountTextView.setText(String.format("%s ★", starCount));
    }
}

RepositoryRowView

interface RepositoryRowView {

    void setTitle(String title);

    void setStarCount(int starCount);
}

All guides I saw was about creating onClickListener object in Adapter and then use it in ViewHolder, but in this implementation, I override all adapter function in my presenter and passing onClickListener (android related stuff) would contradict mvp pattern. What to do in this case. Maybe someone could write a solution - really confused.

My main goal would be to click a recyclerview item and get item name (via toast)

Nibelungenlied answered 31/8, 2017 at 12:36 Comment(0)
U
14

OnClickListener is an interface from Android SDK. Your presenter should not know anything about the Andriod SDK. It should be pure Java so it can be tested just by using Unit test on the JVM. It shouldn't know anything about views, RecyclerView, Adapter nor ViewHolder.

Your onBindViewHolder doesn't violate this principle because it's separated by an abstract interface - RepositoryRowView.

You should implement OnClickListener in adapter/viewholder and call your presenter from there.

public class RepositoryViewHolder extends RecyclerView.ViewHolder implements RepositoryRowView, View.OnClickListener {

    TextView titleTextView;
    TextView starsCountTextView;
    RepositoriesListPresenter presenter;

    public RepositoryViewHolder(View itemView, RepositoriesListPresenter presetner) {
        super(itemView);
        titleTextView = itemView.findViewById(R.id.repoTitleText);
        starsCountTextView = itemView.findViewById(R.id.repoStarsCountText);
        this.presenter = presenter;
        itemView.setOnClickListener(this);
    }

    @Override
    public void setTitle(String title) {
        titleTextView.setText(title);
    }

    @Override
    public void setStarCount(int starCount) {
        starsCountTextView.setText(String.format("%s ★", starCount));
    }

    @Override
    public void onClick(View view) {
        if (presenter != null) {
            presenter.onItemInteraction(getAdapterPosition());
        }
    }
}
Unceremonious answered 31/8, 2017 at 12:50 Comment(2)
Put what if, for instance, I would like to change color of selected item? I can't pass item back to presenter (as you mentioned before), but I could pass the position (as you showed), how would now VIEW (fragment) change color or something with the position?Nibelungenlied
@Nibelungenlied You can use the abstraction you already have in place. You can pass position and RepositoryRowView (which is implemented by your ViewHolder, so you can just add "this" as parameter) to the presenter. Than you can add method to the RepositoryRowView class. Something as "markRowAs..." and its implementation will do the color change.Unceremonious
T
2

Instead of calling the presenter inside your adapter, I would rather make an interface of the click to call it from the view, since you will instantiate this adapter in your view, it's a good thing to keep the MVP pattern with the click of the elements inside your view and not in the adapter itself.

This example is in Kotlin, but I'm sure you will understand it.

First, just make a simple interface to call your click event whenever the user clicks on any item in your list.

class EquipmentAdapter(private val context: Context,private var equipmentList:ArrayList<Equipment>,itemListener:RecyclerViewClickListener): RecyclerView.Adapter<EquipmentAdapter.EquipmentViewHolder>() {

    interface RecyclerViewClickListener {
        fun recyclerViewListClicked(v: View?, position: Int)
    }

    companion object{
        var itemClickListener: RecyclerViewClickListener? = null
        var equipmentSearchList:ArrayList<Equipment>? = null
    }

    init {
        equipmentSearchList = equipmentList
        itemClickListener = itemListener
    }

Then , inside your ViewHolder you should call this interface to handle the click

inner class EquipmentViewHolder(itemView: View): RecyclerView.ViewHolder(itemView), View.OnClickListener {
    val equipmentName:TextView = itemView.txt_equipmentname

    init {
        itemView.setOnClickListener(this)
    }

    override fun onClick(v: View?) {
        itemClickListener?.recyclerViewListClicked(v, adapterPosition)
    }
}

Lastly, just implement the interface of the click in the view that you are calling the adapter, and then just manage the presenter interactions there instead inside the adapter

class EquipmentActivity : BaseActivity(), EquipmentContract.EquipmentView, EquipmentAdapter.RecyclerViewClickListener ...

And implement the click method

override fun recyclerViewListClicked(v: View?, position: Int) {
    presenter.onItemInteraction(position)
}

Doing this, you are making sure that the click of the elements in the list are being made from the view itself and not from the adapter, here, you can interact with the presenter as always and also do more things that will keep your project clean.

Tinned answered 6/6, 2019 at 18:12 Comment(1)
This answer should have been marked as correct.Differentiate

© 2022 - 2024 — McMap. All rights reserved.