Dagger hilt 2.42
I am trying to provide this class using lazy dagger injection.
class AlgoliaAnalyticsProvider @Inject constructor(
private val clientInsights: Lazy<ClientInsights>,
private val coroutineDispatcherProvider: CoroutineDispatcherProvider
)
In my AppModule I have the following:
@Singleton
@Provides
fun provideClientInsights(remoteConfigProvider: RemoteConfigProvider): Lazy<ClientInsights> {
return lazy { ClientInsights(
ApplicationID(BuildConfig.ALGOLIA_APP_ID),
APIKey(remoteConfigProvider.keyAlgoliaApiKey)
)}
}
I also have this where I pass the AlgoliaAnalyticsProvider
@Singleton
@Provides
fun provideAnalyticsProvider(
firebaseAnalyticsProvider: FirebaseAnalyticsProvider,
crashlyticsUserPropertiesProvider: CrashlyticsUserPropertiesProvider,
caMPAnalyticsProvider: CaMPAnalyticsProvider,
algoliaAnalyticsProvider: AlgoliaAnalyticsProvider
) = AnalyticsProvider(
listOf(
firebaseAnalyticsProvider,
crashlyticsUserPropertiesProvider,
algoliaAnalyticsProvider
)
)
I keep getting this error:
error: [Dagger/MissingBinding] kotlin.Lazy<? extends com.algolia.search.client.ClientInsights> cannot be provided without an @Provides-annotated method.
public abstract static class SingletonC implements CDSApplication_GeneratedInjector,
^
kotlin.Lazy<? extends com.algolia.search.client.ClientInsights> is injected at
com.centraldepartment.app.analytics.provider.AlgoliaAnalyticsProvider(clientInsights, …)
com.centraldepartment.app.analytics.provider.AlgoliaAnalyticsProvider is injected at
com.centraldepartment.app.analytics.AnalyticsModule.provideAnalyticsProvider(…, algoliaAnalyticsProvider, …)
But if I comment out the code below it seems to work:
@Singleton
@Provides
fun provideAnalyticsProvider(
firebaseAnalyticsProvider: FirebaseAnalyticsProvider,
crashlyticsUserPropertiesProvider: CrashlyticsUserPropertiesProvider,
caMPAnalyticsProvider: CaMPAnalyticsProvider,
// algoliaAnalyticsProvider: AlgoliaAnalyticsProvider
) = AnalyticsProvider(
listOf(
firebaseAnalyticsProvider,
crashlyticsUserPropertiesProvider,
// algoliaAnalyticsProvider
)
)
You'll need to change your AlgoliaAnalyticsProvider constructor to accept Lazy<@JvmSuppressWildcards ClientInsights>
instead of Lazy<ClientInsights>
. This isn't anything specific about Lazy, other than that it involves a @Provides
method returning something with generics in Kotlin.
Generics in Java and Kotlin are tricky, as described in the "Variance" section in Kotlin's Generics docs. Kotlin provides in
and out
as "variance annotations" that help reason better than the generic wildcards (Lazy<? extends Foo>
) that Java provides. However, even without those variance annotations, generics cause enough interop trouble that Kotlin's default interop behavior is to add the wildcards by default:
To make Kotlin APIs work in Java, the compiler generates
Box<Super>
asBox<? extends Super>
for covariantly definedBox
(orFoo<? super Bar>
for contravariantly definedFoo
) when it appears as a parameter. When it's a return value, wildcards are not generated, because otherwise Java clients will have to deal with them (and it's against the common Java coding style).
This would be OK for your usage—a Lazy<? extends ClientInsights>
would perfectly allow you to get a ClientInsights or subclass where you need it in AlgoliaAnalyticsProvider—but as in your error message Dagger searches unsuccesfully for a binding for kotlin.Lazy<? extends ClientInsights>
and will not accept a binding for kotlin.Lazy<ClientInsights>
. To suppress Kotlin's default behavior, you'll need to apply the workaround on that same interop page above: to add @JvmSuppressWildcards
onto your parameter. Then your call site generics will exactly match your @Provides
return value and Dagger will apply your binding correctly.
Wildcard difficulties are a known pain point in Dagger's consumption of Kotlin as in open issue google/dagger#2586 and the comment that prompted it. There is a visible warning message in the docs, but only on the multibindings page as the multibindings-created collections are also subject to this hazard.
To be clear, the above answer is about the Kotlin builtin lazy
. Separately, Dagger has a built-in wrapper class called Lazy, which works much like Provider but will only call through to the @Provides
method or @Inject
constructor the first time per Lazy instance. For any binding T in your graph, Dagger will automatically make Provider<T>
, dagger.Lazy<T>
, and Provider<dagger.Lazy<T>>
available.
This might actually be a better match for your needs: Your @Provides
method would directly return ClientInsights, which you could then inject as your choice of ClientInsights, Provider<ClientInsights>
, or Lazy<ClientInsights>
; internally, Dagger can also avoid creating a Provider<kotlin.Lazy<ClientInsights>>
, since Dagger can directly return the same internal DoubleCheck implementation it uses to enforce the @Singleton
annotation you added. However, if you don't want to be able to directly inject ClientInsights and only inject Lazy<@JvmSuppressWildcards ClientInsights>
instead, then simply adding the annotation is probably the best way to go.