Эфес вводит дочерний класс как родительский тип

У меня 3 репозитория:

interface MainRepository {
        ...
    }

interface LocalRepository {
        ...
    }

interface WebRepository {
        ...
    }

Каждый репозиторий имеет собственную реализацию:

@Singleton
class MainRepositoryImpl @Inject constructor(
    private val localRepository: LocalRepository,
    private val webRepository: WebRepository
) : MainRepository {
...
}

@Singleton
class LocalRepositoryImpl @Inject constructor(
    private val localMapper: LocalMapper
    private val popularMovieDao: PopularMovieDao
) : LocalRepository {
...
}

@Singleton
class WebRepositoryImpl @Inject constructor(
    private val webMapper: WebMapper,
    private val popularMovieApi: PopularMovieApi
) : WebRepository {
...
}

Как видите, MainRepository необходимо внедрить в него оба других репозитория, однако я не могу понять, как это сделать.

Конечно, я могу ввести его с типом LocalRepositoryImpl или WebRepositoryImpl, но я хочу ввести его с типом LocalRepository или WebRepository для более общего подхода.

Вот модуль, который я пытался написать:

@InstallIn(ApplicationComponent::class)
@Module
object Module {

    @Singleton
    @Provides
    fun provideWebRepository(): WebRepository {
        return WebRepositoryImpl(mapper = WebMapper(), popularMovieApi = PopularMovieApi.getInstance())
    }

    @Singleton
    @Provides
    fun provideLocalRepository(): LocalRepository {
        return LocalRepositoryImpl(mapper = LocalMapper(), // Here I can't really 
            // figure out how to get @Dao since it requires DB 
            // which requires context and etc 
            // which makes me think that I've got completely wrong approach to this)
    }
}

Мой модуль LocalData:

@InstallIn(ApplicationComponent::class)
@Module
object LocalDataSourceModule {
    @Singleton
    @Provides
    fun provideMainDatabase(@ApplicationContext context: Context): MainDatabase = MainDatabase.getInstance(context)

    @Provides
    fun providePopularMovieDao(mainDatabase: MainDatabase): PopularMovieDao = mainDatabase.popularMovieDao()
}

Мой модуль WebData:

@InstallIn(ApplicationComponent::class)
@Module
object RemoteDataSourceModule {

    @Singleton
    @Provides
    fun providePopularMovieApi(): PopularMovieApi = PopularMovieApi.getInstance()
}

Как мне правильно вводить реализации, которые у меня есть (LocalRepositoryImpl & WebRepositoryImpl), сохраняя при этом типы интерфейсов (LocalRepository & `WebRepository) ??


person Vitaliy-T    schedule 11.10.2020    source источник


Ответы (2)


Используйте @Binds. Вместо вашего object Module используйте следующий модуль:

@InstallIn(ApplicationComponent::class)
@Module
interface Module {

    @Binds
    fun bindWebRepository(repository: WebRepositoryImpl): WebRepository

    @Binds
    fun bindLocalRepository(repository: LocalRepositoryImpl): LocalRepository
}

Он сообщает Dagger, что если вам нужна WebRepository зависимость, он должен предоставить WebRepositoryImpl и то же самое для LocalRepository и LocalRepositoryImpl.

Каков вариант использования @Binds против аннотации @Provides в Dagger2

person IR42    schedule 11.10.2020
comment
@ Эндрю, что ты имеешь в виду, @Binds делай именно то, что хочет R'J. И ваше решение ничем не отличается от ручного DI - person IR42; 11.10.2020
comment
Нет! Как сказал Р.Дж., он не хочет WebRepositoryImpl в качестве своих параметров, только WebRepository. Следовательно, он должен использовать @Provides. - person Andrew; 11.10.2020
comment
@Andrew Он имеет ввиду параметры в MainRepositoryImpl ... - person IR42; 11.10.2020
comment
@ Андрей на самом деле, он нужен здесь - person EpicPandaForce; 11.10.2020
comment
Между обоими вашими комментариями у меня есть 2 решения. Решение @Andrew работает, и он дал много идей. Однако ваше решение намного более лаконично и также работает, что я определенно предпочитаю. - person Vitaliy-T; 11.10.2020

Ваши репозитории

interface MainRepository {
        ...
}

interface LocalRepository {
        ...
}

interface WebRepository {
        ...
}

Реализация (здесь нет @Inject или @Singleton!)

class MainRepositoryImpl constructor(
    private val localRepository: LocalRepository,
    private val webRepository: WebRepository
) : MainRepository {
...
}


class LocalRepositoryImpl constructor(
    private val localMapper: LocalMapper
    private val popularMovieDao: PopularMovieDao
) : LocalRepository {
...
}


class WebRepositoryImpl constructor(
    private val webMapper: WebMapper,
    private val popularMovieApi: PopularMovieApi
) : WebRepository {
...
}

Di.Module (модуль репозитория)

@Module
@InstallIn(ApplicationComponent::class)
object RepositoryModule {
    
 @Singleton
 @Provides
 fun provideMainRepository(
           localRepository: LocalRepository,
           webRepository: WebRepository
 ): MainRepository = MainRepositoryImpl(localRepository, webRepository)

 @Singleton
 @Provides
 fun provideLocalRepository(
        localMapper: LocalMapper,
        popularMovieDao: PopularMovieDao
 ): LocalRepository = LocalRepositoryImpl(localMapper, popularMovieDao)

 @Singleton
 @Provides
 fun provideWebRepository(
        webMapper: WebMapper,
        popularMovieApi: PopularMovieApi
 ): WebRepository = WebRepositoryImpl(webMapper, popularMovieApi)

Попробуйте это и скажите, сработало ли это. Поскольку вы предоставили всем своим репозиториям @Provides, Dagger-Hilt знает, как их создавать. Вы используете Room для своей локальной базы данных? Если да, то создание базы данных, как вы, может быть неправильным. Если вы не используете Room, вам следует начать, так как это облегчит вам жизнь.

Вот правильный способ создать базу данных комнаты с рукоятью кинжала:

Модуль Entity

@Entity(tableName = "exampleTableName")
data class ExampleEntity(
    @PrimaryKey(autoGenerate = true)
    val id: Int,
    // ... whatever you need
    val header: String = "",
    val title: String = "",
    val description: String = "",
)

ExampleDatabase

@Database(entities = [ExampleEntity::class], version = 1)
abstract class ExampleDatabase : RoomDatabase() {
    abstract fun exampleDao(): ExampleDao

    companion object {
        const val DATABASE_NAME = "example_db"
    }
}

DAO

@Dao
interface DocumentDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insert(exampleEntity: List<ExampleEntity>)

    @Query("SELECT * FROM exampleTableName")
    suspend fun getList(): List<ExampleEntity>
}

Модуль Диам. Комнаты

@Module
@InstallIn(ApplicationComponent::class)
object RoomModule {

    @Singleton
    @Provides
    fun provideExampleDB(@ApplicationContext context: Context): ExampleDatabase = Room.databaseBuilder(
        context,
        ExampleDatabase::class.java,
        ExampleDatabase.DATABASE_NAME,
    ).fallbackToDestructiveMigration().build()

    @Singleton
    @Provides
    fun provideExampleDAO(exampleDatabase: ExampleDatabase): ExampleDao = exampleDatabase.exampleDao()

  }
person Andrew    schedule 11.10.2020
comment
Я меняю код, когда пишу это. Чтобы ответить на ваш вопрос - да, я использую Room, однако не совсем уверен, как правильно создать базу данных. - person Vitaliy-T; 11.10.2020
comment
Ваше решение сработало как шарм. Также исправлено создание моей БД комнаты. Большое спасибо! - person Vitaliy-T; 11.10.2020