Dagger Hilt Missing/Binding interface with generics

Viewed 1697

I am using Dagger Hilt to provide the dependencies in the app. I have the following classes to implement use cases:

BaseUseCase.kt

interface BaseUseCase<in Parameter, out Result> {
    suspend operator fun invoke(params: Parameter): Result
}

UseCasesModule.kt

@Module
@InstallIn(ViewModelComponent::class)
object UseCasesModule {

    @Provides
    fun provideGetUseCase(
        repositoryImpl: RepositoryImpl
    ): GetUseCase {
        return GeUseCase(repositoryImpl as Repository>)
    }
}

GetUseCase.kt

typealias GetBaseUseCase = BaseUseCase<GetUseCase.Params, Flow<ResultWrapperEntity<List<ModelItem>>>>

class GetUseCase(
    private val repository: Repository
) : GetBaseUseCase {

    override suspend fun invoke(params: Params) =
        repository.get(params.id)

    data class Params(
        val id: Long
    )
}

The problem comes when I try to use the abscraction of GetBaseUseCase in the view model like this:

@HiltViewModel
class ListViewModel @Inject constructor(
    private val useCase: GetUseBaseCase
) : ViewModel()

but it is working fine if I inject the GetUseCase implementation like this:

@HiltViewModel
class ListViewModel @Inject constructor(
    private val useCase: GetUseCase
) : ViewModel()

I am getting the following error:

AppClass_HiltComponents.java:131: error: [Dagger/MissingBinding] com.....BaseUseCase<? super com.....GetUseCase.Params,? extends kotlinx.coroutines.flow.Flow<? extends com.....ResultWrapperEntity<? extends java.util.List<com......ItemModel>>>> cannot be provided without an @Provides-annotated method.

public abstract static class SingletonC implements AppClass_GeneratedInjector,

I have tried to return the abscraction of GetBaseUseCase in the hilt's module but I am getting the same error.

1 Answers

You can constructor inject your GeUseCase implementation and then bind the interface with it.

Reason being dagger doesn't know how to get instance of GetBaseUseCase

Something like

@Singleton // without any scope or with any other scope
class GetUseCase @Inject constructor(
    private val repository: Repository
) : GetBaseUseCase {

    override suspend fun invoke(params: Params) =
        repository.get(params.id)

    data class Params(
        val id: Long
    )
}

and then bind it with your interface

@Module
@InstallIn(ViewModelComponent::class)
interface SomeModule {
  @Binds
  fun bind(usecase: GetUseCase): BaseUseCase<,> (your generic type)
}

It should work fine with generics as well - here's the thread of you run into any issue

https://github.com/google/dagger/issues/2161

If not able to do constructor injection - you can provide and then bind

@Module
@InstallIn(ViewModelComponent::class)
abstract class YourModule {

    @Binds
    abstract fun bind(usecase: GetUseCase): BaseUseCase<,> (your generic type)

    companion object {

        @Provides
        fun providesUseCase(): GetUseCase {
            return GetUseCase(// required dependency)
        }
    }
}

You also need to add @JvmSuppressWildcards - to avoid the error you are getting @Binds methods' parameter type must be assignable to the return type

Here's the link for more info

https://github.com/google/dagger/issues/1143#issuecomment-381776533

Related