Search code examples
androidkotlinandroid-recyclerviewrecyclerview-layout

Generic RecyclerView adapter


I want to have generic RecyclerView to be able to reuse it. In my case I have 2 models: CategoryImages and Category. While trying to add constructor() it brings the following errors. I know the second one is because it understands like both primary and secondary constructor are same.

Is it possible to do such kind of thing? If yes, then how? if no - thank you.

enter image description here

Here is CategoryImage:

class CategoryImage {
   @SerializedName("url")
   private var url: String? = null
       fun getUrl(): String? {
       return url
   }
}

And here is Category:

class Category {
    @SerializedName("_id")
    var id: String? = null
    @SerializedName("name")
    var name: String? = null
    @SerializedName("__v")
    var v: Int? = null
    @SerializedName("thumbnail")
    var thumbnail: String? = null
}

Here is the part of RecyclerViewAdapter's constructor:

class RecyclerViewAdapter(var arrayList: ArrayList<CategoryImage>?, var fragment: Int): RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>() {
     constructor(arrayList: ArrayList<Category>, fragment: Int): this(arrayList, fragment)
}

Solution

  • I want to have generic RecyclerView to be able to reuse it.

    That's nice intention, then why you haven't made your adapter generic?

    I think you can adopt the approach outlined by Arman Chatikyan in this blog post. After applying some Kotlin magic you'll only need following lines of code in order to setup your RecyclerView:

    recyclerView.setUp(users, R.layout.item_layout, {
        nameText.text = it.name
        surNameText.text = it.surname
    })
    

    And if you need to handle clicks on RecyclerView items:

    recyclerView.setUp(users, R.layout.item_layout, {
        nameText.text = it.name
        surNameText.text = it.surname
    }, {
        toast("Clicked $name")
    })
    

    Now the adapter of the RecyclerView is generic and you are able to pass list of any models inside setup() method's first argument.


    In this section I will copy-paste sources from the blog post, in order to be evade from external sources deprecation.

    fun <ITEM> RecyclerView.setUp(items: List<ITEM>,
                                  layoutResId: Int,
                                  bindHolder: View.(ITEM) -> Unit,
                                  itemClick: ITEM.() -> Unit = {},
                                  manager: RecyclerView.LayoutManager = LinearLayoutManager(this.context)): Kadapter<ITEM> {
      return Kadapter(items, layoutResId, {
        bindHolder(it)
      }, {
        itemClick()
      }).apply {
        layoutManager = manager
        adapter = this
      }
    }
    
    class Kadapter<ITEM>(items: List<ITEM>,
                         layoutResId: Int,
                         private val bindHolder: View.(ITEM) -> Unit)
      : AbstractAdapter<ITEM>(items, layoutResId) {
    
      private var itemClick: ITEM.() -> Unit = {}
    
      constructor(items: List<ITEM>,
                  layoutResId: Int,
                  bindHolder: View.(ITEM) -> Unit,
                  itemClick: ITEM.() -> Unit = {}) : this(items, layoutResId, bindHolder) {
        this.itemClick = itemClick
      }
    
      override fun onBindViewHolder(holder: Holder, position: Int) {
        holder.itemView.bindHolder(itemList[position])
      }
    
      override fun onItemClick(itemView: View, position: Int) {
        itemList[position].itemClick()
      }
    }
    
    abstract class AbstractAdapter<ITEM> constructor(
        protected var itemList: List<ITEM>,
        private val layoutResId: Int)
      : RecyclerView.Adapter<AbstractAdapter.Holder>() {
    
      override fun getItemCount() = itemList.size
    
      override fun onCreateViewHolder(parent: ViewGroup,
                                      viewType: Int): Holder {
        val view = LayoutInflater.from(parent.context).inflate(layoutResId, parent, false)
        return Holder(view)
      }
    
      override fun onBindViewHolder(holder: Holder, position: Int) {
        val item = itemList[position]
        holder.itemView.bind(item)
      }
    
      protected abstract fun onItemClick(itemView: View, position: Int)
    
      protected open fun View.bind(item: ITEM) {
      }
    
      class Holder(itemView: View) : RecyclerView.ViewHolder(itemView)
    }