Inject Interface with Generics in Hilt - android

I am trying to use Hilt to inject a generic interface implementation but it keep gives me errors
My interface looks like this:
interface Validator<in T> {
fun validate(value: T): Boolean
}
And a typical implementation of it can be like this:
class FullNameValidator #Inject constructor() : Validator<String>{
override fun validate(value: String): Boolean {
val isValid = //Validation logic
return isValid
}
}
Now the way I am trying to use Hilt, i've tried to use #Bind method in a #Module like this
#Binds
abstract fun bindFullNameValidator(validator: FullNameValidator) : Validator<String>
Like i am doing with all interfaces implementations and its working just fine
But here, Hilt claims that it cannot find a way to inject the String object which is the generic type.
Is there a proper way to inject such interface

Related

Dto map and use-case

how can I make Dto map and pass it to the use-case using clean architecture in android kotlin
i tried to make an object class and pass it to the use-case but it didn't work
Note that I will use HILT for dependency injection ... you can use any di library.
You can make a base interface mapper for objects like this:
interface mapper <T,R> {
fun map(t: T): R
}
Then create a class that implements Mapper interface:
class ProductMapper #Inject constructor() : Mapper<RemoteProduct, ProductModel>() {
override fun map(t: RemoteProduct): ProductModel {
// Pass any argument you want or do some business logic
return ProductModel(t.id, t.name)
}
Now I Suggest injecting this ProductMapper inside the repo when you get the response from API or locale database then return mapped data to the use-case.
But anyway you can inject this mapper inside use-case like this:
class GetProductsUseCase #Inject constructor (private val productsMapper: ProductsMapper) {
// Don't forget to inject your repo too
operator fun invoke(): ProductMode {
return productsMapper.map(repo.getProduct())
}
}
Finally using Hilt create new module to bind ProductMapper in our case there is no need to do that step but if your mapper depends on another classes you need to add it in module like this:
#Module
#InstallIn(SingltonComponent::Class)
interface AppModule {
#Binds
fun bindProductDetailsMapper(productsMapper: ProductsMapper): Mapper
}

Kotlin + Hilt: Class cannot be provided without an #Provides-annotated method when injecting into an object

I was trying to look for similar posts in SO before posting, but most of them talk about retrofit, and my question is about injecting a dependency (Service, Repository or whatever) into an object using #EntryPoint.
I have an object like this:
object FreddieMercuryYouAreTheOne {
lateinit var exception: ExceptionHandler
fun init(appContext: Context) {
setDependencies(appContext)
DoOtherInitStuff...
}
private fun setDependencies(appContext: Context){
val exh = EntryPointAccessors.fromApplication(appContext, Dependencies.ProvideExceptionHandler::class.java)
this.exception = exh.exceptionHandler()
}
/*
* THIS IS JUST AN ABSURD EXAMPLE
* */
private fun DoWhatever(cryptKey16CharStr: String, cryptInitializationVector16CharStr: String) {
try {
doWhatever
}catch(ex: Exception){
exception.logException(ex)
}
}
}
And then I have the class where I set the dependencies:
#Module
#InstallIn(SingletonComponent::class)
class Dependencies {
#EntryPoint
#InstallIn(SingletonComponent::class)
interface ProvideExceptionHandler {
fun exceptionHandler(): ExceptionHandler
}
}
And when building, what I get is the following error:
error: [Dagger/MissingBinding] exception.ExceptionHandler cannot be provided without an #Provides-annotated method.
Well, if I modify my dependencies module as follows:
#Module
#InstallIn(SingletonComponent::class)
class Dependencies {
#Provides
#Singleton
fun bindsExceptionHandler(): ExceptionHandler {
return ExceptionHandler
}
#EntryPoint
#InstallIn(SingletonComponent::class)
interface ProvideExceptionHandler {
fun exceptionHandler(): ExceptionHandler
}
}
Not only build, but it works, and ExceptionHandler is correctly injected in FreddieMercuryYouAreTheOne object, so, as you see, what I have is not exactly an issue, but wondering to know why I need two "providers" to be able to inject a dependency into an object, lets say, why is not enough with interface ProvideExceptionHandler (as Google documentation mentions).
I ask this because I have many class objects across my app, and most of them have dependencies, and so this way I'll have to create two providers for each dependency. Am I doing something wrong?
Entry Points used for field injection for un-supported classes by Hilt like a custom class or content provider.
in your case since you have object FreddieMercuryYouAreTheOne thats can't has a constructer . yeah you need :
1- to Provide the object(instance) you want
in your case:
#Provides
#Singleton
fun bindsExceptionHandler(): ExceptionHandler {
return ExceptionHandler
}
2- and then say hey!! ,i need field injection in my custom class
then you should use :
#EntryPoint
#InstallIn(SingletonComponent::class)
interface ProvideExceptionHandler {
fun exceptionHandler(): ExceptionHandler
}
if you have a normal class you just need to provide the object(just point #1). and then inject it in the constructer.
as i say #EntryPoint for un-supported classes field injection just.
Hint the recommended is constructer-injection over field injection
PLUS: ExceptionHandler and most of dependencies should be injected into ViewModel

Dagger 2 MissingBinding when swapping concretion for interface

I have two classes that I'm able to have Dagger find and inject for me to use successfully:
TrackEvent
class TrackEvent #Inject constructor(
private val getTrackingProperties: SomeClass
) : UseCase<Boolean, TrackingEvent> {
override suspend operator fun invoke(params: TrackingEvent): Boolean {
return true
}
SomeClass (note: used as a dependency in TrackEvent)
class SomeClass #Inject constructor() {
override suspend operator fun invoke(): UserTrackingPropertiesResult {
return UserTrackingPropertiesResult()
}
}
TrackEvent has an entry in an #Module annotated interface because it's an implementation of the UseCase interface:
#Component(modules = [MyModule::class])
interface ShiftsComponent {
fun inject(homeFragment: HomeFragment)
}
#Module
interface MyModule {
#Binds
fun bindsTrackEventUseCase(useCase: TrackEvent): UseCase<Boolean, TrackingEvent>
}
Use Case interfaces
interface UseCase<out T, in P> {
suspend operator fun invoke(params: P): T
}
interface NoParamUseCase<out T> {
suspend operator fun invoke(): T
}
What I'd like to do is to inject an interface into TrackEvent instead of the concrete SomeClass. So I make SomeClass implement a NoParamUseCase
class SomeClass #Inject constructor(): NoParamUseCase<UserTrackingPropertiesResult> {
override suspend operator fun invoke(): UserTrackingPropertiesResult {
return UserTrackingPropertiesResult()
}
}
update TrackEvent to inject the interface:
class TrackEvent #Inject constructor(
private val getTrackingProperties: NoParamUseCase<UserTrackingPropertiesResult>) : UseCase<Boolean, TrackingEvent> {
override suspend operator fun invoke(params: TrackingEvent): Boolean {
return true
}
}
…and update MyModule to inform Dagger of which implementation I'd like to use:
#Module
interface MyModule {
#Binds
fun bindsTrackEventUseCase(useCase: TrackEvent): UseCase<Boolean, TrackingEvent>
// New
#Binds
fun bindsSomeClass(useCase: SomeClass): NoParamUseCase<UserTrackingPropertiesResult>
}
Dagger now claims that there is a missing binding and that I need to declare an #Provides annotated method:
error: [Dagger/MissingBinding] com.myapp.core.domain.usecase.NoParamUseCase<? extends com.myapp.core.tracking.UserTrackingPropertiesResult> cannot be provided without an #Provides-annotated method.
public abstract interface MyComponent {
^
com.myapp.core.domain.usecase.NoParamUseCase<? extends com.myapp.core.tracking.UserTrackingPropertiesResult> is injected at
com.myapp.tasks.tracking.domain.usecase.TrackEvent(getTrackingProperties, …)
…
As far as I can tell, this isn't true:
While, I've opted for #Binds in this instance, replacing this with #Provides and manually providing dependencies here yields the same error.
I'm using the exact same approach for the TrackEvent class and this works.
The only thing I've changed is that I'd like to provide an interface instead. I'd fully understand this error had I not provided the #Binds declaration.
This is different to this question as there's no ambiguity as to which implementation I'm asking Dagger to use in the way that there would be if I had two or more implementations of the same interface.
Why would I get this error now?
According to dagger error message, it expects covariant type NoParamUseCase<? extends UserTrackingPropertiesResult>, but DI module provides invariant NoParamUseCase<UserTrackingPropertiesResult>. To generate appropriate signature for provide method you can change it like this:
#Binds fun bindsSomeClass(useCase: SomeClass): NoParamUseCase<#JvmWildcard UserTrackingPropertiesResult>
After that your code should be compiled successfully.

Dagger2 creating a map of concrete instances fails with "cannot be provided without an #Provides-annotate"

I am playing around with Dagger collection, in particular with map.
I want to use Dagger2 to inject a map whose key is an enum and the values a concrete
implementation of an interface. The map
is injected in a presenter. An unique component instantiates the presenter, an
activity uses the instantiation to display the strings produced by the value
of the map.
I get the following error:
e: ../DaggerMap/app/build/tmp/kapt3/stubs/debug/com/aklal/briquedagger2/MainComponent.java:7: error: [Dagger/MissingBinding] java.util.Map<com.aklal.briquedagger2.Lapse.TIME,? extends com.aklal.briquedagger2.Lapse.ChristmasTime> cannot be provided without an #Provides-annotated method.
public abstract interface MainComponent {
^
java.util.Map<com.aklal.briquedagger2.Lapse.TIME,? extends com.aklal.briquedagger2.Lapse.ChristmasTime> is injected at
com.aklal.briquedagger2.MainPresenter(mapOfLapse)
com.aklal.briquedagger2.MainPresenter is injected at
com.aklal.briquedagger2.MainModule.getMainPresenter(connection)
com.aklal.briquedagger2.Presenter is provided at
com.aklal.briquedagger2.MainComponent.presenter()
FAILURE: Build failed with an exception.
The "project" can be found here
Implementation
I have an interface ChristmasTime which is implemented by two classes:
TimeUntilNextChristmas
TimeSinceLastChristmas
These implementation are similarly simply defined as follow:
class TimeSinceLastChristmas #Inject constructor(): ChristmasTime {
override fun getLapseOfTime() = "SINCE TEST"
}
I want to let Dagger2 create a map with
an enum value as key
ChristmasTime as type value
The key value is defined as follow:
#MapKey
annotation class TimeKey(val value: TIME)
enum class TIME {
UNTIL,
SINCE
}
I created a module to provide concrete implementations of type ChristmasTime:
#Module
interface LapseOfTimeModule {
#Binds
#IntoMap
#TimeKey(TIME.UNTIL)
fun provideLapsesOfTimeUntil(t: TimeUntilNextChristmas): ChristmasTime
#Binds
#IntoMap
#TimeKey(TIME.SINCE)
fun provideLapsesOfTimeSince(t: TimeSinceLastChristmas): ChristmasTime
}
I want to displayed the string returned by the concrete implementations on the
screen. To do so, a presenter communicates with an activity the strings contained
in the map (that has been injected in the presenter):
class MainPresenter #Inject constructor(
private val mapOfLapse: Map<TIME, ChristmasTime>
) : Presenter {
override fun getDateUntil(): String = mapOfLapse[TIME.UNTIL]?.getLapseOfTime() ?: "UNTIL FAILED"
}
The component to instantiate the presenter in the MainActivity takes the module that defines the map (LapseOfTimeModule) and a MainModule
#Component(modules = [MainModule::class, LapseOfTimeModule::class])
interface MainComponent {
fun presenter(): Presenter
}
MainModule is:
#Module
interface MainModule {
#Binds
fun getMainPresenter(connection: MainPresenter): Presenter
}
And the MainActivity is:
class MainActivity : AppCompatActivity() {
#Inject
lateinit var presenter: Presenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
presenter = DaggerMainComponent.create().presenter()
bttDisplayNewDate.setOnClickListener{
displayDate(
presenter.getDateSince(),
presenter.getDateUntil()
)
}
}
fun displayDate(since: String, until: String) {
tvSince.text = since
tvUntil.text = until
}
}
Does anyone know how to fix that ?
Here are some threads that I read but they did not help much:
Kotlin dagger 2 Android ViewModel injection error
Dagger2 Inherited subcomponent multibindings
Thanks in advance!!
ps: The version of dagger used is 2.24 and kotlin version is 1.3.41
It's a little bit tricky how it works. In Java it will work. Kotlin decompile Map<K,V> just to Map, and Dagger can't find a Map without types of K and V. To fix it please use just java.util.Map for autogenerated daggers class.
class MainPresenter #Inject constructor(
private val mapOfLapse: java.util.Map<TIME, ChristmasTime>
) : Presenter {
of course, then you need to map it into kotlin map to have all extension functions.

Dagger: How to provide specific implementation of a parametrized base type?

I'm new to Dagger and have the following setup:
// data models
open class BaseEntity (open val id: Long)
data class UserEntity (override val id: Long, val name: String) : BaseEntity(id)
data class FruitEntity (override val id: Long, val status: String) : BaseEntity(id)
// interface to spec common API response operations
interface Repo<T> {
fun getEntities(): List<T>
}
// entity specific implementation of the repo interface
class RepoImpl<T: BaseEntity> #Inject constructor(apiService: ApiService) : Repo<T> {
override fun getEntities(): List<T> {
val entities = apiService.getEntities(...)// get result from respective entity network service, e.g users, fruits etc
return entities
}
}
// DI: provide entity-specific implementations of the interface
#Singleton
#Provides
fun provideUserRepoImpl(userService: UserApiService): RepoImpl<BaseEntity> {
return RepoImpl(userService)
}
#Singleton
#Provides
fun provideFruitRepoImpl(fruitService: FruitApiService): RepoImpl<BaseEntity> {
return RepoImpl(fruitService)
}
When I build the project, this error comes up:
error: RepoImpl<com.example.data.model.BaseEntity> is bound multiple times
...
#org.jetbrains.annotations.NotNull #Provides #Singleton com.example.data.source.remote.RepoImpl<com.example.data.model.BaseEntity> com.example.app.di.AppModule.provideUserRepoImpl()
#org.jetbrains.annotations.NotNull #Provides #Singleton com.example.data.source.remote.RepoImpl<com.example.data.model.BaseEntity> com.example.app.di.AppModule.provideFruitRepoImpl()
And when I try to provide the entity-specific instances, like this:
#Singleton
#Provides
fun provideUserRepoImpl(userService: UserApiService): RepoImpl<UserEntity> {
return RepoImpl(userService)
}
#Singleton
#Provides
fun provideFruitRepoImpl(fruitService: FruitApiService): RepoImpl<FruitEntity> {
return RepoImpl(fruitService)
}
I get the following:
error: RepoImpl<com.example.data.model.BaseEntity> cannot be provided without an #Inject constructor or from an #Provides-annotated method
Also I have tried separating the provider methods of fruits & users into their respective modules but latter error also comes up, maybe I'm not hooking up the FruitModule, UserModule and AppModule correctly. I'm having similar problems providing implementations of interfaces.
What would be a correct approach here, or is it not at all possible to inject parametrized classes with Dagger?
UPDATE
I have managed to solve the dependency issue on RepoImpl by explicitly providing each object to be injected (the latter option where we provide RepoImpl<UserEntity> and RepoImpl<FruitEntity>). I think the #Inject annotation on the constructor was somehow not being registered,
so had to refresh the project.
However, I cannot figure out how to provide implementations (or sub-types) of parametrized interfaces just yet.
Consider for example the ApiService parameter of RepoImpl defined as follows:
// base service for all types of items
interface ApiService<T: BaseAttributes> {
fun getEntities(): T
}
// service impl for fruits
class FruitService : ApiService<FruitAttributes> {
override fun getEntities(): FruitResponses {...}
}
// service impl for users
class UserService : ApiService<UserAttributes> {
override fun getEntities(): UserResponses {...}
}
// AppModule.kt:
#Singleton
#Provides
fun provideUserService() : ApiService<UserAttributes> {
return UserService()
}
#Singleton
#Provides
fun provideFruitService() : ApiService<FruitAttributes> {
return FruitService()
}
error: "... ApiService<BaseAttributes> cannot be provided without an #Provides-annotated method ..."
whereas
...
fun provideUserService() : ApiService<BaseAttributes> {
return UserService() as ApiService<BaseAttributes>
}
...
fun provideFruitService() : ApiService<FruitAttributes> {
return FruitService() as ApiService<BaseAttributes>
}
error: "... ApiService<BaseAttributes> is bound multiple times: ..."
How else can I inject these implementations of the interface?
Turns out there's a well-discussed wildcard issue on dagger/kotlin generics. Particularly, the "cannot be provided without an #Provides-annotated method" error on parametrized classes requires suppressing generation of wildcard types (covariants or contravariants) with the #JvmSuppressWildcard annotation at the injection site. So I would have done:
class RepoImpl<T: BaseEntity> #Inject constructor(apiService: #kotlin.jvm.JvmSuppressWildcards ApiService) : Repo<T> { ... }
Though I actually ended up converting RepoImpl into an abstract class and creating concrete ones for Fruit & User types and providing them at the module-level instead:
class UserRepoImpl #Inject constructor(apiService: UserService) : RemoteRepoImpl(apiService)
class FruitRepoImpl #Inject constructor(apiService: FruitService) : RemoteRepoImpl(apiService)
This related SO question contains another example.
Finally, this Kotlin+Dagger thread contains some nice gotchas and tips relevant to this problem

Categories

Resources