Search code examples
androidgenericskotlinkotlin-extension

Kotlin generics expecting unexpected type


I've defined the following base class with two generic types and using it two levels deep (for lack of a better phrase). Here's my use case.

abstract class BasePresenter<out M, out V> {
    var model: M? = null

    var view: WeakReference<V>? = null

    fun setM(model: M?): Unit {
        this.model = model

        if (setupDone()) {
            updateView()
        }
    }

    fun bindView(view: V) {
        this.view = WeakReference(view)
    }

    fun unbindView() {
        this.view = null
    }

    abstract fun updateView()

    fun view(): V? {
        return if (view == null) null else view?.get()
    }

    fun setupDone(): Boolean {
        return view() != null && model != null
    }
}

I'm extending it using

open class VenueListPresenter: BasePresenter<Venue, VenueView>()  {...}

Which works fine, as expected, but then I'm running into issues when I'm trying to use the VenuListPresenter as a type parameter in a different class.

open class VenuesViewHolder(itemView: View): MvpViewHolder<VenueListPresenter>(itemView) {

This gives me an error stating that the expected argument in MvpViewHolder is BasePresenter, and that what was found was a VenueListPresenter. My VenueListPresenter extends a BasePresenter<Venue, VenueView> where Venue and VenueView are of type Any? because by default they extend it. So why isn't it working?

MvpViewHolder is defined like so

abstract class MvpViewHolder<P>(itemView: View) : RecyclerView.ViewHolder(itemView) where P : BasePresenter<Any?, Any?>

Solution

  • You need to add out variance to your generic parameters in BasePresenter so that a type like BasePresenter<Venue, VenueView> will be a subtype of BasePresenter<Any?, Any?>.

    abstract class BasePresenter<out M, out V>
    

    As a short explanation, the out keyword specifies covariance, e.g. if you have a class YourClass<out T>, it means that when A is a subtype of B, then YourClass<A> is also a subtype of YourClass<B>.

    See more details about Kotlin generics and variance in the docs.


    Edit based on comment below:

    If you can't make the above change, you could use use site variance at the MvpViewHolder class instead, to accept BasePresenter subtypes with any subtypes of Any? in their type parameters:

    abstract class MvpViewHolder<P>(itemView: View) : RecyclerView.ViewHolder(itemView) 
            where P : BasePresenter<out Any?, out Any?>
    

    You could do this exact same thing with star projection (just a different syntax in this case):

    abstract class MvpViewHolder<P>(itemView: View) : RecyclerView.ViewHolder(itemView) 
            where P : BasePresenter<*, *>
    

    In both of these cases, anything of type V or M returned from a P will be of type Any?, and you won't be able to pass V or M instances into methods of P. If you need to be able to do that, you could consider adding more generic parameters:

    abstract class MvpViewHolder<P, M, V>(itemView: View) : RecyclerView.ViewHolder(itemView)
            where P : BasePresenter<M, V>