Как различать несколько реализаций одного интерфейса в Dagger
Что проверяют
Знаете ли вы, как Dagger находит зависимость: по ключу из типа и квалификатора. Если две реализации привязаны к одному интерфейсу без квалификатора, граф не соберётся. Следом часто спрашивают, чем @Named хуже своего квалификатора и как получить сразу все реализации.
Как отвечать
Ключ привязки в Dagger это пара «тип плюс квалификатор». Две привязки Repository без квалификатора дают ошибку компиляции о дублирующейся привязке, поэтому реализации нужно пометить:
@Named("remote"): встроенный квалификатор со строкой. Работает, но опечатка всплывёт только при сборке графа, а искать использования по строке неудобно;- свой квалификатор:
@Qualifier @Retention(AnnotationRetention.BINARY) annotation class Remote. Типобезопасно, IDE находит все места, так же сделано в документации Hilt; @Bindsв абстрактном модуле:@Binds @Remote abstract fun bindRemote(impl: RemoteRepository): Repository. Дешевле@Provides, Dagger не генерирует лишний фабричный метод;- мультибиндинги: если нужны все реализации,
@IntoSetсоберётSet<Repository>, а@IntoMapсо@StringKeyили@ClassKeyдастMap, из которого реализацию выбирают в рантайме, например способ оплаты по типу.
В месте внедрения квалификатор ставится на параметр: class Sync @Inject constructor(@Remote private val repo: Repository). В Hilt всё то же самое, модуль только помечается @InstallIn(SingletonComponent::class).
Где спотыкаются
Внедряют в поле Kotlin через @Inject @Remote lateinit var repo. В зависимости от версии Kotlin аннотация без явной цели может уйти на свойство, а не на поле, и Dagger её не увидит. Надёжный вариант для полей: @field:Remote.
Ставят квалификатор на привязку, а в месте внедрения забывают. Получают ошибку MissingBinding, хотя модуль на месте, и долго ищут проблему не там.
Путают два сценария. Квалификатор нужен, когда реализация известна при компиляции. Если её выбирают по условию во время работы, пачка @Named не поможет, нужен Map из мультибиндинга.