29

What makes me puzzled is how to bind view in Recycleler.ViewHolder. This is my simple adapter and how to Convert it to kotlin use kotlin-android-extensions without ButterKnife?

public class RoomAdapter extends RecyclerView.Adapter<ViewHolder> {

  private OnItemClickListener mListener;
  private List<LocationBean> mRooms;

  static class ViewHolder extends RecyclerView.ViewHolder {

  @BindView(R.id.tv_title)
  TextView tvTitle;

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

  public void setData(List<LocationBean> rooms) {
   mRooms = rooms;
   notifyDataSetChanged();
  }

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

  @Override
  public void onBindViewHolder(final ViewHolder holder, int position) {
  holder.tvTitle.setText(mRooms.get(position).getLocation());

  holder.itemView.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
      mListener.onItemClickListener(v, holder.getLayoutPosition());
     }
    });
  }

  @Override
  public int getItemCount() {
    return mRooms == null ? 0 : mRooms.size();
  }

  public void setOnItemClickListener(OnItemClickListener listener) {
    mListener = listener;
  }

  public interface OnItemClickListener {
    void onItemClickListener(View v, int pos);
  }

}
shiguiyou
  • 401
  • 1
  • 5
  • 7
  • Possible duplicate of [Kotlin synthetic in Adapter or ViewHolder](https://stackoverflow.com/questions/33304570/kotlin-synthetic-in-adapter-or-viewholder) – Nathan Reline May 26 '17 at 02:53
  • 1
    You should really try to do the port and show specific problems you had in doing so. Otherwise you are asking for a service of "please port my code for me" – Jayson Minard May 26 '17 at 17:26

3 Answers3

53

The posted solution works, but I'd like to add something to it. The purpose of the viewholder pattern is to only make the expensive findViewById calls once for every view, and then hold those references inside the ViewHolder, and access the views from there whenever you need to bind one.

However, calling holder.itemView.tv_title.text in the onBindViewHolder method will cause a findViewById call to find the View that has the id tv_title within the itemView every time the viewholder is bound. This basically eliminates the performance gains and caching idea that viewholders are for.

You can make use of the viewholder pattern and Kotlin Android Extensions at the same time by adding a property to your ViewHolder, and initializing it with a call made with Extensions, like so:

class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val title = itemView.tv_title
}

Then you can access the View through this property:

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    holder.title.text = mRooms!!.get(position).getLocation()

    holder.itemView.setOnClickListener { v ->
        mListener?.onItemClickListener(v, holder.layoutPosition)
    }
}

Lastly, I'd suggest getting rid of the !! operator, and performing a null check instead:

mRooms?.let { rooms ->
    holder.title.text = rooms[position].getLocation()
}
zsmb13
  • 85,752
  • 11
  • 221
  • 226
  • For those of you using kotlin synthetics, this is a great article that shows how to avoid findViewById calls in onBindViewHolder: https://proandroiddev.com/kotlin-android-extensions-using-view-binding-the-right-way-707cd0c9e648 – 0xMatthewGroves Jun 18 '19 at 03:32
  • correct, because KAE does not always call the optimized method "findCachedViewById", for example this ViewHolder case. – superuser Jul 19 '20 at 09:21
9

Just make sure you have apply plugin: 'kotlin-android-extensions' in your application gradle file (not your root gradle) and then just use the Convert Java file to Kotlin file shortcut. Remove the Butterknife code and directly reference itemView.tv_title in your ViewHolder object like so.

Edit: I've changed the code to take advantage of Kotlin's synthetic view caching, you don't need to set a variable equal to a view like val title = itemView.tv_title in order to gain those benefits

import kotlinx.android.synthetic.main.item_first_select.view.*

class RoomAdapter : RecyclerView.Adapter<RoomAdapter.ViewHolder>() {

    private var listener: OnItemClickListener? = null
    private var rooms: List<LocationBean>? = null

    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        fun bind(room: Room) {
            itemView.tv_title.text = room.getLocation()
        }

    }

    fun setData(rooms: List<LocationBean>) {
        this.rooms = rooms
        notifyDataSetChanged()
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_first_select, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        rooms?.get(position)?.let { room ->
            holder.bind(room)
        }

        holder.itemView.setOnClickListener { v ->
            listener?.onItemClickListener(v, holder.layoutPosition)
        }
    }

    override fun getItemCount(): Int {
        return rooms?.size ?: 0
    }

    fun setOnItemClickListener(listener: OnItemClickListener) {
        this.listener = listener
    }

    interface OnItemClickListener {
        fun onItemClickListener(v: View, pos: Int)
    }

}
Nathan Reline
  • 1,137
  • 10
  • 13
2

Others answered the question quite well, I just wanted to add that in class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) there should be no nullable operator ('?') after itemView for kotlin binding to work.

Android Studio automatically adds that nullable operator when using autocomplete for adding constructer parameters from ViewHolder(itemView).

Ali Nem
  • 5,252
  • 1
  • 42
  • 41