Ashu
Ashu

Reputation: 3382

How to extend kotlin generic class without specifying type parameter to allow any subtypes for recycler view?

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

Answers (3)

Tenfour04
Tenfour04

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

Maks Gwizdała
Maks Gwizdała

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

Yang Liu
Yang Liu

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

Related Questions