1

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?>
Rafa
  • 3,219
  • 4
  • 38
  • 70

1 Answers1

3

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>
zsmb13
  • 85,752
  • 11
  • 221
  • 226
  • I was reading about them earlier and didn't go with using `out` because it specified that that's used for producer classes. In my case, (I have made the edits for you to see the entire presenter class) having them be prefixed with `out` caused the line `var model: M? = null` to get a compile error saying that it occurs as an invariant position in type M. It looks like making it private solves the issue, but do I always have to be conscious of how I'm using the types within the class in order to declare them as out or in? – Rafa Nov 01 '17 at 18:09
  • In the class I posted, I use it as both a producer and consumer – Rafa Nov 01 '17 at 18:15