1

In the below code if we use generic in base and then extend it in a diff interface, kotlin doesn't respect the generic of the base interface.

Why is that so?

In the base I have not used "in" or "out" but still the extended interface by default becomes "out".

interface FeaturedCardAdapterContract {
interface View {
    fun onCreate()
  }

interface SubPresenter<V : View> {
    fun onBind(v: V)
  }
}

interface FeaturedTestAdapterContract {
   interface View : FeaturedCardAdapterContract.View
   interface Presenter : FeaturedCardAdapterContract.SubPresenter<View>
}

fun main() {
val featureImpl1: FeaturedTestAdapterContract.Presenter = object : FeaturedTestAdapterContract.Presenter {
    override fun onBind(v: FeaturedTestAdapterContract.View) {
    }
}
val featureImpl2: FeaturedTestAdapterContract.Presenter = object : FeaturedTestAdapterContract.Presenter {
    override fun onBind(v: FeaturedTestAdapterContract.View) {
    }
}

//Works but i won't be able to consume it in onBind bcz kotlin assumed it as "out"
val interfaceArray: Array<FeaturedCardAdapterContract.SubPresenter<out FeaturedCardAdapterContract.View>> = arrayOf(featureImpl1, featureImpl2)

//Dosen't Work-bcz kotlin assumes the type of featureImpl1 is FeaturedCardAdapterContract.SubPresenter<out FeaturedCardAdapterContract.View> ,Why?
val interfaceArray: Array<FeaturedCardAdapterContract.SubPresenter<FeaturedCardAdapterContract.View>> = arrayOf(featureImpl1, featureImpl2)

//Works but,Same as 1st method
val interfaceArray: Array<FeaturedCardAdapterContract.SubPresenter<*>> = arrayOf(featureImpl1, featureImpl2)

for (featureImpl in interfaceArray) {
    //Won't work bcz of "out"
    featureImpl.onBind(object : FeaturedCardAdapterContract.View {
        override fun onCreate() {
            //
        }
    })
}
}
Prateek Kumar
  • 321
  • 1
  • 3
  • 16
  • Both lines "works" and "doesn't work" are identical, the first 2 `interfaceArray` definitions are the same, isn't there a typo somewhere? Didn't you mean for them to be different? – Joffrey Jun 02 '22 at 11:15
  • Sorry , Fixed it @Joffrey – Prateek Kumar Jun 02 '22 at 11:16
  • Seeing the names `SubPresenter` and `Presenter`, could it be that you reversed the inheritance hierarchy here? You have defined `Presenter` as a subtype of `SubPresenter`, so I'm a bit confused about your goal. – Joffrey Jun 02 '22 at 11:36
  • Out of curiosity, why do you think this would work in the first place? – Sweeper Jun 02 '22 at 11:43
  • @Joffrey SubPresenter is just for simplicity purpose , should have named it something else , but same thing works in Java if it is something like "interface SubPresenter – Prateek Kumar Jun 02 '22 at 11:47
  • And just for the goal, In one screen we have multiple adapters with few things in common, so we have a base adapter interface and for every adapter, we extend that base interface(this is a basic idea). The code is in java,am just doing kotlin conversion so and finding it hard to do so. – Prateek Kumar Jun 02 '22 at 11:50
  • @PrateekKumar Java is broken in this respect. It will not catch all typing errors, while Kotlin is more accurate. As Sweeper showed wonderfully in their answer, what you're trying to do here breaks type safety (you would pass animals to a processor that only accepts dogs) – Joffrey Jun 02 '22 at 11:56
  • @Joffrey Now I got it with his example, so basically even in java a force type cast was happening and it did not show any error. – Prateek Kumar Jun 02 '22 at 11:58

2 Answers2

3

Rename the interfaces to Processor, Animal, and Dog, and you will see why the compiler is correct about the types and what you are trying to do doesn't make sense.

Here's the renaming:

interface Animal // FeaturedCardAdapterContract.View

interface Processor<A: Animal> { // FeaturedCardAdapterContract.SubPresenter<V>
    fun process(animal: A) // onBind
}

interface Dog: Animal // FeaturedTestAdapterContract.View
interface DogProcessor: Processor<Dog> // FeaturedTestAdapterContract.Presenter

In main, you are creating an array of 2 DogProcessors:

val processorImpl1 = object: DogProcessor {
    override fun process(animal: Dog) {

    }
}

val processorImpl2 = object: DogProcessor {
    override fun process(animal: Dog) {

    }
}

val array = arrayOf(processorImpl1, processorImpl2)

Then you are trying to loop through it and have them each process an animal:

val array = arrayOf(processorImpl1, processorImpl2)
for (processor in array) {
    processor.process(object: Animal {

    })
}

This is obviously not going to work no matter how you change the type of array. The processors in the array process dogs specifically, not animals in general. You could simply make this work by just giving it dogs instead of animals, or in your case:

val interfaceArray = arrayOf(featureImpl1, featureImpl2)

for (featureImpl in interfaceArray) {
    featureImpl.onBind(object : FeaturedTestAdapterContract.View {
        override fun onCreate() {
            //
        }
    })
}

Note that the type of the array can be changed to Array<Processor<out Animal>> - an array of processors that only produces animals. This is because a producer of dogs is a kind of producer of animals. See also: PECS. However, since you want to call process (onBind) here, you want the processor to take in, or consume an animal, not produce one. Therefore, Array<Processor<out Animal>> is not what you want.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • I hesitated to translate the types into clearer ones, and now I can see it would have been a good idea :) The message is probably much clearer this way. – Joffrey Jun 02 '22 at 11:45
  • Thanks a lot, @Sweeper, this really made everything very clear to me.Thanks again, man. – Prateek Kumar Jun 02 '22 at 12:00
2

Just to clarify, you have defined featureImpl1 as FeaturedTestAdapterContract.Presenter, so it's a FeaturedCardAdapterContract.SubPresenter<FeaturedTestAdapterContract.View>.

Note the "Test" view here, not the "Card" one. This is your own definition of Presenter - the View you use in the definition is a shortcut for the test view FeaturedTestAdapterContract.View, NOT the card one FeaturedCardAdapterContract.View:

val featureImpl1: FeaturedTestAdapterContract.Presenter = object : FeaturedTestAdapterContract.Presenter {
    // only wants test views here
    override fun onBind(v: FeaturedTestAdapterContract.View) {
}

Now check this part:

Won't work bcz of "out"

featureImpl.onBind(object : FeaturedCardAdapterContract.View {
    //...
})

Let's forget about out for the moment. You have defined your featureImpl1 so it accepts to bind only to the specific FeaturedTestAdapterContract.View. But here you're trying to pass a card view FeaturedCardAdapterContract.View, which is NOT a test view. If this were allowed, the body of featureImpl1 would just fail because it is given objects that are NOT of type FeaturedTestAdapterContract.View, nor even subtypes of it.

//Works but i won't be able to consume it in onBind bcz kotlin assumed it as "out"
val interfaceArray: Array<FeaturedCardAdapterContract.SubPresenter<out FeaturedCardAdapterContract.View>> = arrayOf(featureImpl1, featureImpl2)

Kotlin didn't assume anything here, you're marking out yourself. But it's normal that you have to write it because of what I explained above.

We've just seen that featureImpl1 is a SubPresenter<FeaturedTestAdapterContract.View>. It cannot be assigned to a SubPresenter<FeaturedCardAdapterContract.View> (without out) because that would mean it would need to accept more types than it actually can.

Joffrey
  • 32,348
  • 6
  • 68
  • 100