Reputation: 3382
So I have the following type of RecyclerView
implementation in various places in my app code:
package com.test;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.BaseViewHolder> {
@NonNull
private List<? extends ListItem> itemList;
public ItemAdapter(@NotNull final List<? extends ListItem> itemList) {
this.itemList = itemList;
}
@NonNull
@Override
public BaseViewHolder onCreateViewHolder(@NonNull final ViewGroup viewGroup, final int itemType) {
final Context context = viewGroup.getContext();
final BaseViewHolder BaseViewHolder;
final View view = new View(context); // imagine an inflated view here
switch (itemType) {
case ListItem.TYPE_ONE:
BaseViewHolder = new TypeOneViewHolder(view);
break;
case ListItem.TYPE_TWO:
BaseViewHolder = new TypeTwoViewHolder(view);
break;
default: // try to get coupon view holder if item type valid
throw new IllegalArgumentException("Invalid view type");
}
return BaseViewHolder;
}
@Override
public void onBindViewHolder(@NonNull final BaseViewHolder baseViewHolder, final int position) {
baseViewHolder.bind(itemList.get(position));
}
@Override
public int getItemCount() {
return itemList.size();
}
@Override
public int getItemViewType(final int position) {
return itemList.get(position).getItemType();
}
public interface ListItem {
int TYPE_DEF = -1;
int TYPE_ONE = 0;
int TYPE_TWO = 1;
int getItemType();
}
public static class ItemOne implements ListItem {
@Override
public int getItemType() {
return TYPE_ONE;
}
}
public static class ItemTwo implements ListItem {
@Override
public int getItemType() {
return TYPE_TWO;
}
}
public static abstract class BaseViewHolder<T extends ListItem> extends RecyclerView.ViewHolder {
BaseViewHolder(@NonNull View itemView) {
super(itemView);
}
public abstract void bind(final T item);
}
public static class TypeOneViewHolder extends BaseViewHolder<ItemOne> {
TypeOneViewHolder(@NonNull View itemView) {
super(itemView);
}
@Override
public void bind(ItemOne item) {
// do something...
}
}
public static class TypeTwoViewHolder extends BaseViewHolder<ItemTwo> {
TypeTwoViewHolder(@NonNull View itemView) {
super(itemView);
}
@Override
public void bind(ItemTwo item) {
// do something...
}
}
}
Now it works perfectly for me in java. But I am having a hard time trying to implement the same thing in Kotlin. Kotlin doesn't allow me to just say public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.BaseViewHolder>
without asking me for the type parameter with BaseViewHolder
. I have tried to use kotlin in
or out
variants or use <*>
but something or the other gives me problems.
So, my question is, is there a way to implement the above pattern of recycler view in kotlin? If not, how should I approach the similar functionality?
Upvotes: 0
Views: 1014
Reputation: 93922
The way you are using it in Java is you that you are declaring the BaseListItem with a raw type in your ItemAdapter. So it's not using generics in a safe way. When you call baseViewHolder.bind(itemList.get(position))
, you are implicitly casting whatever the item is at that list position to the type of the view holder that is at that place in the list. Kotlin forbids raw types because it defeats the purpose of using generics. Java only allows raw types because of backward compatibility.
Your Java code works, because presumably your list items are reporting their correct item types, which are used in onCreateViewHolder
. So the casting only causes an issue if you made a mistake there.
You would get equivalent functionality in Kotlin (or in Java) by removing the generic type from your BaseListItem class, and making your bind
function look like this:
abstract fun bind(item: ListItem)
class TypeOneViewHolder: BaseViewHolder() {
fun bind(item: ListItem){
val itemOne = item as ItemOne
// use itemOne for binding
}
}
If you don't like the unsafe cast warning, you can do a safe cast:
fun bind(item: ListItem){
(item as? ItemOne)?.let { itemOne ->
// use itemOne for binding
} ?: error("Item type mismatch. ${this::class} with ${item::class}")
}
Alternatively, if you want to continue using your pattern and let RecyclerView.Adapter do the unsafe casting on your behalf under the hood, I think you can set it up using a star projection. Since your own code doesn't ever call any methods on your BaseViewHolder implementations, Kotlin shouldn't have anything to complain about. I didn't try this myself, but maybe it will work:
abstract class BaseViewHolder<in T: ListItem>(itemView: ItemView): ViewHolder(itemView) {
abstract fun bind(item: T)
}
class ItemAdapter(val itemList: List<out ListItem>): Adapter<BaseViewHolder<*>>(){
//...
}
Upvotes: 2
Reputation: 1
I belive you can type parameter with BaseViewHolder:
public class ItemAdapter : RecyclerView.Adapter<ItemAdapter.BaseViewHolder<ListItem>>
And in your BaseViewHolder just type generic parameter T, then cast it inside e.g. bind() method. Similar code works in my case:
public abstract class BaseViewHolder<in T>(view: View) : RecyclerView.ViewHolder(view){}
Upvotes: 0
Reputation: 1390
Does it work for you by changing ? extends ListItem
and T extends ListItem
to just ListItem
? This should work in kotlin.
One of the example can be found here: https://stackoverflow.com/a/59071476/8493280.
Otherwise, you can use the type Any
which is the same as Object
in java. It will make the Adapter generic, even though that may not be what you really want.
Upvotes: 0