1

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?

HBG
  • 1,731
  • 2
  • 23
  • 35
  • is GetUser already provide? or has constructor inject? – Công Hải May 02 '20 at 10:19
  • @hai I've removed all of the additional dependencies now. They weren't relevant and the issue still persists even in this simplified form. – HBG May 02 '20 at 12:47
  • @HBG, to solve problem try add `@JvmSuppressWildcards` to the signature of function inside your DI module like this `@Binds fun bindsSomeClass(useCase: SomeClass): NoParamUseCase<@JvmSuppressWildcards UserTrackingPropertiesResult>` – ConstOrVar May 02 '20 at 21:30
  • Does this answer your question? [Dagger 2 - two provides method that provide same interface](https://stackoverflow.com/questions/39953933/dagger-2-two-provides-method-that-provide-same-interface) – denvercoder9 May 02 '20 at 22:06
  • Dagger doesn't know which implementation to provide for that interface with just a `@binds` annotation. You need to explicitly tell dagger if you don't want to write a `@provides` concrete function. To do that you need to either add a `@named` annotation or your own custom `@qualifier` – denvercoder9 May 02 '20 at 22:08
  • @sonnet Thanks but this didn't seem to have any impact. I've a number of similar places in the code where I've needed to be more specific about which implementation I'd like Dagger to use - this doesn't appear to be one of them. I only have one implementation that adheres to this type. Nonetheless, I tried your solution and it made no impact. – HBG May 03 '20 at 05:29
  • @ConstOrVar Adding `@JvmSuppressWildcards` does indeed seem to solve the issue - but not when added to the Module. It only works when added to `TrackEvent` directly: `val getTrackingProperties:NoParamUseCase<@JvmSuppressWildcards UserTrackingPropertiesResult>`. Does `UserTrackingPropertiesResult` being a `sealed class` have some sort of impact here? – HBG May 03 '20 at 05:33
  • @HBG I think, it relates to how kotlin and dagger compiler work in conjunction in case of generation code for generics. Will it helps if you modify `@Binds fun bindsSomeClass(useCase: SomeClass): NoParamUseCase` - explicitly adding `out` modifier? – ConstOrVar May 03 '20 at 20:58
  • @ConstOrVar The `out` variance is already specified on the interface so this has no effect. Would you like to provide an answer with the above and claim the points/ glory? – HBG May 04 '20 at 06:36
  • @HBG, I've investigated a little bit and found solution to that problem: `@Binds fun bindsSomeClass(useCase: SomeClass): NoParamUseCase<@JvmWildcard UserTrackingPropertiesResult>` - this instructs kotlin compiler to generate appropriate signature to resolve dependencies. In my test project it helps to eliminate the issue. And that solution allow to not modify your target classes, only DI specific module. If it'll resolve problem, give me feedback and I post it as answer with advanced explanation – ConstOrVar May 04 '20 at 10:53
  • @ConstOrVar Yes, this works and prevents me from having to modify the injection site. Thanks – HBG May 04 '20 at 21:23

1 Answers1

1

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.

ConstOrVar
  • 2,025
  • 1
  • 11
  • 14