Remiwaw
Remiwaw

Reputation: 183

Generics in kotlin. How to pass implementation of Abstract class with generics

I am struggling with generics / interfaces / implementation in kotlin.

The question is why I cannot to pass a implementation to HabitsRepository like this:

class HabitsRepository(habitDao: HabitDao, habitConverter: HabitConverter) :
    AbstractRepository<AbstractDataSource<AbstractEntity>, AbstractConverter<AbstractModel, AbstractEntity>>(
        habitDao,
        habitConverter
    )

I got following error

enter image description here

My setup is following:

HabitsRepository

 class HabitsRepository(habitDao: HabitDao, habitConverter: HabitConverter) :
    AbstractRepository<AbstractDataSource<AbstractEntity>, AbstractConverter<AbstractModel, AbstractEntity>>(
        habitDao as AbstractDataSource<AbstractEntity>,
        habitConverter as AbstractConverter<AbstractModel, AbstractEntity>
    )

HabitDao

@Dao
interface HabitDao : AbstractDataSource<Habit>{

    @Query("SELECT * FROM habit WHERE id=:id ")
    override fun getById(id: String): Single<Habit>

    @Query("SELECT * FROM habit")
    override fun getAll(): Single<List<Habit>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    override fun insert(entity: AbstractEntity): Completable

    @Delete
    override fun delete(habit: AbstractEntity) : Completable

    @Update
    override fun update(habit: AbstractEntity): Completable
}

HabitConverter

class HabitConverter : AbstractConverter<HabitModel, Habit> {
    override fun toModel(entity: Habit): HabitModel {
        return HabitModel()
    }

    override fun toEntity(model: HabitModel): Habit {
        return Habit()
    }
}

HabitDatabaseSource

class HabitDatabaseSource @VisibleForTesting constructor(private val habitDao: HabitDao) : HabitDao {

    override fun getById(id: String): Single<Habit> = habitDao.getById(id)

    override fun getAll(): Single<List<Habit>> = habitDao.getAll()

    override fun insert(entity: AbstractEntity): Completable = habitDao.insert(entity)

    override fun delete(habit: AbstractEntity): Completable = habitDao.delete(habit)

    override fun update(habit: AbstractEntity): Completable = habitDao.update(habit)
}

AbstractDataSource

interface AbstractDataSource<T : AbstractEntity> {
    fun getById(id: String): Single<T>
    fun getAll(): Single<List<T>>
    fun insert(entity: AbstractEntity): Completable
    fun delete(entity: AbstractEntity): Completable
    fun update(entity: AbstractEntity): Completable
}

AbstractEntity / AbstractModel

abstract class AbstractEntity
abstract class AbstractModel

AbstractConverter

interface AbstractConverter<AM: AbstractModel, AE: AbstractEntity>{
    fun toModel(entity: AE) : AM
    fun toEntity(model: AM): AE
}

AbstractRepository

abstract class AbstractRepository<ADS: AbstractDataSource<AbstractEntity>, AC: AbstractConverter<AbstractModel, AbstractEntity>>(
    private val abstractDataSource: ADS,
    private val abstractConverter: AC
){
    fun getById(id: String): Single<AbstractModel> {
        return abstractDataSource.getById(id).map { abstractConverter.toModel(it) }
    }

    fun getAll(): Single<MutableList<AbstractModel>> =
        abstractDataSource.getAll().flattenAsObservable {it}.map { abstractConverter.toModel(it) }.toList()

    fun insert(model: AbstractModel): Completable = abstractDataSource.insert(abstractConverter.toEntity(model))

    fun delete(model: AbstractModel): Completable = abstractDataSource.delete(abstractConverter.toEntity(model))

    fun update(model: AbstractModel): Completable = abstractDataSource.update(abstractConverter.toEntity(model))
}

Upvotes: 0

Views: 351

Answers (1)

Tenfour04
Tenfour04

Reputation: 93872

HabitDao is not an AbstractDataSource<AbstractEntity>. It is defined as AbstractDataSource<Habit>. When a generic type is not defined as in or out, then it is invariant, meaning you must pass a parameter with the exact type, not a supertype or subtype. There is a similar issue for your HabitConverter and both of its superclass's generic types.

And taking it even a step farther, your HabitDao is a subclass of AbstractDataSource so you are breaking invariance at two levels.

Your HabitRepository must declare the exact types of the Habit classes it is using:

class HabitsRepository(habitDao: HabitDao, habitConverter: HabitConverter) :
    AbstractRepository<HabitDao, HabitConverter>(
        habitDao,
        habitConverter
    )

At least with your AbstractRepository, the generic types only occur in producing positions, so you could declare their types as out without losing any functionality:

abstract class AbstractRepository<out ADS: AbstractDataSource<AbstractEntity>, out AC: AbstractConverter<AbstractModel, AbstractEntity>> ( //...

and then you could be less strict in your HabitsRepository definition:

class HabitsRepository(habitDao: HabitDao, habitConverter: HabitConverter) :
    AbstractRepository<AbstractDataSource<Habit>, AbstractConverter<HabitModel, Habit>>(
        habitDao,
        habitConverter
    )

But AbstractDataSource and AbstractConverter are both producers and consumers of their generic types, so their types have to be invariant.

Upvotes: 4

Related Questions