Problem with Kotlin Generics in a ListAdapter

934 Views Asked by At

I am trying to right a Generic ListAdapter for RecyclerView. I have 3 things I want to pass into the Adapter. The List of items, the row layout to use and the ViewHolder. I am able to get the list generically and the layout, but its the ViewHolder. Here is what I have so far but I am still new to Generics in Kotlin. I tried using the Class out method and then I am having issues with calling the constructor for a specific viewholder.

abstract class AbstractListAdapter(
    private val items: List<*>, 
    private val layoutId: Int, 
    private val viewHolderClass: ???? >
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(){

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val v = LayoutInflater.from(parent.context).inflate(layoutId, parent, false)
        return viewHolderClass.??? // need to call the constructor of the specific viewholder passed in
    }

    override fun getItemId(position: Int): Long {
        return position.toLong()
    }

    override fun getItemViewType(position: Int): Int {
        return position
    }

    override fun getItemCount(): Int {
        return items.size
    }

}

// List adapter uses abstract
class ListAdapter(private items: List<Files>, private val id: Int, private viewHolder: FileViewHolder) : AbstractListAdapter(items, id, fileViewHolder) {

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
             ... some code here for the viewholder and list combining
    }
}

// Specific VH that want to pass into abstract generic yet it will call the constructor from there.
class FileViewHolder(fileView: View) {
   .... grab views for specific layout
}
1

There are 1 best solutions below

2
On BEST ANSWER

You maybe do this by taking a ViewHolder constructor instead of class. That way you won't have to use reflection to instantiate the ViewHolder. You need to have a generic type for the ViewHolder, so your subclasses can properly implement onBindViewHolder and have access to the specific type of ViewHolder.

Also, you must make the items property have a type, or you won't be able to use it. And your subclass probably needs to be able to access it, so it needs to be protected, not private.

I did not test this:

abstract class AbstractListAdapter<VH: RecyclerView.ViewHolder> (
    protected val items: List<*>, 
    private val layoutId: Int, 
    private val viewHolderConstructor: (View) -> VH >
) : RecyclerView.Adapter<VH>(){

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val v = LayoutInflater.from(parent.context).inflate(layoutId, parent, false)
        return viewHolderConstructor(v)
    }

    //...
}

Then to implement it, you specify the ViewHolder constructor in the superconstructor call and you specify the type. Since the type is specified, you don't need a constructor parameter for it in the subclass.

class ListAdapter(private items: List<Files>, private val id: Int) : AbstractListAdapter<FileViewHolder>(items, id, ::FileViewHolder) {

    override fun onBindViewHolder(holder: FileViewHolder, position: Int) {
        //...
    }
}

That said, I'm not sure what this actually achieves for you. It just seems like moving code around. You will still have to extract view references from the parent view. You just have to do it in the ViewHolder initialization instead of in onCreateViewHolder. And now you will have to be careful to pass in the right layout that matches up with the specific ViewHolder type. You may as well remove that parameter and do the layout in the ViewHolder constructor to avoid that issue. But now all you've done is move the onCreateViewHolder functionality into your ViewHolder's init block.

Also, your version of the abstract class is subverting expected results of the functions you've overridden. Why would every item in the list have a different type? Why would the item ID's be based on list position? This just messes up the functionality for editing the list data (rearranging, adding and removing will be broken).