0

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))
}

1 Answer 1

4

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.