Yasheed Mohammed
Yasheed Mohammed

Reputation: 212

RecyclerView with multiple view types with Generics

I'm trying to achieve multiple view types in a recycler view using a generic base view holder.

The BaseViewHolder class is

abstract class BaseViewHolder<T>(itemView: View) : RecyclerView.ViewHolder(itemView) {
    abstract fun bind(item: T)
}

Then I created a view holder class for a specific view type

inner class CarouselViewHolder(itemView: View) : BaseViewHolder<HomeCarousel>(itemView) {
    override fun bind(item: HomeCarousel) {
    }
}

I'm getting an error when accessing this bind function from fun onBindViewHolder method.

Out-projected type 'BaseViewHolder<*>' prohibits the use of 'public abstract fun bind(item: T):

 override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) {
    val element = data[position]
    when (element) {
        is HomeCarousel -> {
            holder.bind(element)
        }
        else -> throw java.lang.IllegalArgumentException("Invalid binding")
    }
}

Getting error at

       holder.bind(element)

This is my entire Adapter class

class HomePageRecylerAdapter(private val data: ArrayList<Any>) : RecyclerView.Adapter<BaseViewHolder<*>>() {

companion object {
    const val typeCarousel = 0
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<*> {
    return when (viewType) {
        typeCarousel -> {
            val view =
                LayoutInflater.from(parent.context).inflate(R.layout.recycler_item_home_carasoul, parent, false)
            CarouselViewHolder(view)
        }
        else -> throw IllegalArgumentException("Invalid view type")
    }

}

override fun getItemCount() = data.size

override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) {
    val element = data[position]
    when (element) {
        is HomeCarousel -> {
            holder.bind(element)
        }
        else -> IllegalArgumentException("Invalid binding")
    }
}



override fun getItemViewType(position: Int): Int {
    val element = data[position]
    return when (element) {
        is HomeCarousel -> typeCarousel
        else -> throw IllegalArgumentException("Invalid type of data {$position}")
    }
}
inner class CarouselViewHolder(itemView: View) : BaseViewHolder<HomeCarousel>(itemView) {
    override fun bind(item: HomeCarousel) {
    }
}
}
abstract class BaseViewHolder<T>(itemView: View) : 
RecyclerView.ViewHolder(itemView) {
   abstract fun bind(item: T)
} 

Upvotes: 2

Views: 2557

Answers (1)

shivanshs9
shivanshs9

Reputation: 96

TLDR;

Well, here's a code which should work:

class HomePageRecylerAdapter(private val data: ArrayList<Any>) : RecyclerView.Adapter<BaseViewHolder<Any>>() {

    companion object {
        const val typeCarousel = 0
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<Any> {
        return when (viewType) {
            typeCarousel -> {
                val view =
                        LayoutInflater.from(parent.context).inflate(0, parent, false)
                CarouselViewHolder(view) as BaseViewHolder<Any>
            }
            else -> throw IllegalArgumentException("Invalid view type")
        }

    }

    override fun getItemCount() = data.size

    override fun onBindViewHolder(holder: BaseViewHolder<Any>, position: Int) {
        val element = data[position]
        when (element) {
            is HomeCarousel -> {
                holder.bind(element)
            }
            else -> IllegalArgumentException("Invalid binding")
        }
    }



    override fun getItemViewType(position: Int): Int {
        val element = data[position]
        return when (element) {
            is HomeCarousel -> typeCarousel
            else -> throw IllegalArgumentException("Invalid type of data {$position}")
        }
    }
    inner class CarouselViewHolder(itemView: View) : BaseViewHolder<HomeCarousel>(itemView) {
        override fun bind(item: HomeCarousel) {
        }
    }
}
abstract class BaseViewHolder<in T: Any>(itemView: View) :
        RecyclerView.ViewHolder(itemView) {
    abstract fun bind(item: T)
}

Explanation

Possibly this answer may help give you more insights - https://stackoverflow.com/a/51110484/2674983

Although, I find the justification in the answer wrong. The issue is not that the adapter is written in Java, but rather it requires both the subtype of viewholder to be returned and also passes it as argument. Otherwise, it would be easy to use something like use-site variance.

I think the correct way to solve it would've been to use contravariance:

abstract class BaseViewHolder<in T>(itemView: View) :
        RecyclerView.ViewHolder(itemView) {
    abstract fun bind(item: T)
}

That's because the object of type T can by anything and it needs to pass to a setter, so we only need to set that into the ViewHolder. However, contrapositive has an interesting twist... subtyping works in opposite direction now! So, now BaseViewHolder<HomeCarousel> is no longer a subtype of BaseViewHolder<Any>, since the subtyping is reversed.

To fix it, we could do "unsafe casting", as I've done in the code.

Oh, and using BaseViewHolder<*> (star-projection) does solve subtyping issues, since every instance of BaseViewHolder is now a subtype but it places the restrictions of both covariance and contravariance, so not useful here.

Upvotes: 4

Related Questions