Reputation: 212
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
Reputation: 96
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)
}
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