Out-projected type prohibits the use of method

2.1k views Asked by At

I can't seem to wrap my head around Kotlin generics, please help

I read here and here and I still can't determine how to make this work. I have a RecyclerView adapter that uses an abstracted parent BaseFieldVH as the ViewHolder class.

So the Adapter class looks like this:

class MyAdapter() : RecyclerView.Adapter<BaseFieldVH<*>>() {
... 
    override fun onBindViewHolder(holder: BaseFieldVH<*>, position: Int) {
        holder.bind(data[position])
    }
}

And the ViewHolder implementation looks like this:

abstract class BaseFieldVH<in FF : BaseFormField>
    (itemView: View) : RecyclerView.ViewHolder(itemView) {
    ...
    abstract fun bind(formField: FF)
}

But the actual call to holder.bind(data[position]) is displaying the error:

Out-projected type 'BaseFieldVH<*>' prohibits the use of 'public 
abstract fun bind(formField: FF): Unit defined in ...

use in Nothing?

I've also tried defining the Adapter with BaseFieldVH<in Nothing> but then I get a Type Mismatch error for attempting to put Nothing into a function that requires a BaseFormField.

use BaseFormField

Defining the Adapter with BaseFieldVH<iBaseFormField> actually resolves the binding issue, but then in the onCreateViewHolder there is a type mismatch for the view holders:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseFieldVH<BaseFormField> {
    val itemView: View
    when (viewType) {
        HEADER_TYPE -> {
            itemView = LayoutInflater.from(parent.context)
                    .inflate(R.layout.item_header, parent, false)
            return HeaderVH(itemView)
        }

HeaderVH is an extension of BaseFieldVH, and so there's a type mismatch

2

There are 2 answers

0
mitenko On BEST ANSWER

It looks like the answer to this issue is that you can't implement a ViewHolder class that specifies a generic for binding. My guess is this arises from the fact that the RecyclerView.Adapter is written in Java and any Kotlin extension is too restrictive to implement it properly.

If you can contradict this conclusion, let me know!

I found this post which explains the whole projection concept in a way that I could understand.

In the end I abandoned my attempt to control the Type for the ViewHolder:

abstract class BaseFieldVH
  (itemView: View) : RecyclerView.ViewHolder(itemView) {
  ...
  abstract fun bind(formField: Any)
}

This means each child class of BaseFieldVH has to do some a type check in its bind function. For instance:

class HeaderVH
  (itemView: View) : BaseFieldVH(itemView) {
...
  override fun bind(headerField: Any) {
    if (headerField is HeaderField) {
      // do something
    }
}

}

But now at least I'm not getting any errors.

0
aleksandrbel On

If you have multiple complex objects in data list (in order to show different items in the recycler view) you also need to specify you holder.

The idea here is that you provide Adapter, not a Holder to decide how you need to handle each object type.

class MyAdapter() : RecyclerView.Adapter<BaseFieldVH<*>>() {
    ... 

    override fun onBindViewHolder(holder: BaseFieldVH<*>, position: Int) {
        val item = data[position]

        holder as BaseFieldVH<DATA_ITEM_TYPE>
        item as DATA_ITEM_TYPE

        holder.bind(item)
    }
}