I have the following definitions:
#Module
class WeaverDataModule {
// Provide the three pumps from providers
// All of them still explicitly mark 'Pump' as their return type
#Provides #IntoSet fun providesPump(thermosiphon: Thermosiphon) : Pump = thermosiphon
#Provides #IntoSet fun providesAnotherPump(suctionBased: SuctionBased) : Pump = suctionBased
#Provides #IntoSet fun providesGenericPump(genericPump: GenericPump) : Pump = genericPump
}
#Component(modules = [WeaverDataModule::class])
interface WeaverData {
// Get the CoffeeMaker
fun coffeeMaker(): CoffeeMaker
// Get the list of pumps
fun getPumps() : Set<Pump>
}
interface Pump
// The three pumps
class Thermosiphon #Inject constructor(val heater: Heater) : Pump
class SuctionBased #Inject constructor() : Pump
class GenericPump #Inject constructor() : Pump
// Some random heater
class Heater #Inject constructor()
In my code, when I do the following:
val cm = DaggerWeaverData.builder().build().getPumps()
I do get the three pumps as expected. However, when I'm trying to inject it into some other class:
class CoffeeMaker #Inject constructor(
private val heater: Heater,
private val pump: Set<Pump>
) {
fun makeCoffee() =
"Making coffee with heater ${heater::class.java} and using pumps" +
" ${pump.map { it::class.java }.joinToString(",")}"
}
I get the following error:
e: .../WeaverData.java:7: error: [Dagger/MissingBinding] java.util.Set<? extends weaver.Pump> cannot be provided without an #Provides-annotated method.
public abstract interface WeaverData {
^
java.util.Set<? extends weaver.Pump> is injected at
weaver.CoffeeMaker(…, pump)
weaver.CoffeeMaker is provided at
weaver.WeaverData.coffeeMaker()
I've tried injecting Collection<Pump> also, but I still get a similar error. In the dagger docs on multibinding, the example (in Java) shows the following:
class Bar {
#Inject Bar(Set<String> strings) {
assert strings.contains("ABC");
assert strings.contains("DEF");
assert strings.contains("GHI");
}
}
which is exactly what I'm doing. And for constructor-based injection, it is working just fine in Kotlin, because the following compiles and runs as expected:
class CoffeeMaker #Inject constructor(
private val heater: Heater
) {
fun makeCoffee() =
"Making coffee with heater ${heater::class.java}"
}
So I'm kind of at a loss on how do I get this multibinding to work.
So turns out what you need to do is:
class CoffeeMaker #Inject constructor(
private val heater: Heater,
private val pumps: Set<#JvmSuppressWildcards Pump>
) {
fun makeCoffee() =
"Making coffee with heater ${heater::class.java} with pumps ${pumps.map { it::class.java }.joinToString(",")}"
}
This is because Set is defined in Kotlin as Set<out E> which translates into Java as Set<? extends Pump>. From a type-theory perspective, Set<? extends Pump> is different from Set<Pump> and hence Dagger (probably) refuses to see Set<Pump> as an injectable for Set<? extends Pump>, which is fair and the right behavior.
The problem we have is that for any of these collections, since they are immutable by default, a declaration of type Set<X> will translate to Set<? extends X>, as an immutable collection only has references to the resolved type on returns and is hence covariant. To verify this theory, the following also works:
class CoffeeMaker #Inject constructor(
private val heater: Heater,
private val pumps: MutableSet<Pump>
) {
fun makeCoffee() =
"Making coffee with heater ${heater::class.java} with pumps ${pumps.map { it::class.java }.joinToString(",")}"
}
Note the use of MutableSet, which is defined as MutableSet<E> : Set<E> .... This is probably not something one should use because I doubt that this set is actually mutable. So what we do need is for the kotlin compiler to treat Set<out E> as Set<E> (the assignabiliy is valid in this case, just not the other way around). So do so, we use the #JvmSuppressWildcards annotation. I hope this helps somebody else facing similar issues.
Related
The Code A is from the end branch of the official sample project.
The project use Hilt to implement dependency injection.
I searched the the whole project, I find only the code annotation class DefaultDispatcher, but I can't understand what the code mean.
Where is the defaultDispatche instanced by Hilt ?
Code A
#HiltViewModel
class MainViewModel #Inject constructor(
private val destinationsRepository: DestinationsRepository,
#DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher
) : ViewModel() {
...
}
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun CraneHomeContent(
onExploreItemClicked: OnExploreItemClicked,
openDrawer: () -> Unit,
modifier: Modifier = Modifier,
viewModel: MainViewModel = viewModel(),
) {
...
}
#Retention(AnnotationRetention.BINARY)
#Qualifier
annotation class DefaultDispatcher
You should have a module declared like this
#Module
object DispatcherModule {
#DefaultDispatcher
#Provides
fun providesDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
}
Now you create this class in order to use #DefaultDispatcher as an annotation
#Retention(AnnotationRetention.BINARY)
#Qualifier
annotation class DefaultDispatcher
For more details, visit here
In here on your sample project, that you have shared is perfectly written like this.
This means #DefaultDispatcher will be used as an annotation, where value will be provided by the DispatcherModule
I'm trying to use ActivityScoped and ActivityComponent in a simple project, but i was getting below error
#javax.inject.Named("String2") java.lang.String cannot be provided without an
#Provides-annotated method.
public abstract static class SingletonC implements BaseApplication_GeneratedInjector,
#javax.inject.Named("String2") java.lang.String is injected at
com.example.hiltoplayground.TestViewModel(testString)
com.example.hiltoplayground.TestViewModel is injected at
com.example.hiltoplayground.TestViewModel_HiltModules.BindsModule.binds(vm)
#dagger.hilt.android.internal.lifecycle.HiltViewModelMap
java.util.Map<java.lang.String,javax.inject.Provider<androidx.lifecycle.ViewModel>>
is requested at
dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.ViewModelFactoriesEntryPoint.getHiltViewModelMap() [com.example.hiltoplayground.BaseApplication_HiltComponents.SingletonC → com.example.hiltoplayground.BaseApplication_HiltComponents.ActivityRetainedC → com.example.hiltoplayground.BaseApplication_HiltComponents.ViewModelC]
But if i use SingletonComponent and #Singleton it runs perfect, no issue.
Here is the affected module
#Module
#InstallIn(ActivityComponent::class)
object MainModule {
#ActivityScoped
#Provides
#Named("String2")
fun provideTestString2 (
#ApplicationContext context: Context
) = context.getString(R.string.string_inject)
}
Below is the ViewModel (No problem here i guess)
#HiltViewModel
class TestViewModel #Inject constructor(
#Named("String2") testString: String): ViewModel() {
init{
Log.d("String2Message","Show $testString")
}
}
You can use #ActivityRetainedScoped in place of #ActivityScoped and make #InstallIn(ActivityRetainedComponent::class) from #InstallIn(ActivityComponent::class).
Is there a way to inject instances of a Kotlin inline class with Dagger? In other words is there any way to get this code to compile assuming that we have a Dagger component that includes UserModule?
inline class Username(val name: String)
#Module
class UserModule {
#Provides
fun provideUsername(): Username = Username("default_user")
}
class MyClass #Inject constructor(private val username: Username)
I am specifically trying to do this on Android using Hilt. Not sure if that matters or not.
Same here - wanted this instead of injecting a "Named" or class instance wrapper string via Hilt:
#JvmInline
value class ApplicationFlavor #Inject constructor(private val flavor: String) {
fun isPaid() = flavor.equals("paid", true)
fun isFree() = flavor.equals("free", true)
fun trial() = flavor.equals("trail", true)
}
but decided to settle for using a class instance since I couldn't seem to find a workaround:
class ApplicationFlavor...
#Provides
#Singleton
fun provideApplicationFlavor() = ApplicationFlavor(BuildConfig.FLAVOR)
Is there a way to implement something similar to Lombok's annotation #slf4j with annotation class in Kotlin?
Right now I have an extension function that instantiates a Logger Factory for me, and I must create these variables in each of my classes just like the example below:
#RestController
#RequestMapping("/api/v1/sample")
class SampleController() {
private val log = logger()
#GetMapping
fun show(): String {
log.info("SOME LOGGING MESSAGE")
return "OK"
}
}
inline fun <reified T> T.logger(): Logger {
if (T::class.isCompanion) {
return LoggerFactory.getLogger(T::class.java.enclosingClass)
}
return LoggerFactory.getLogger(T::class.java)
}
What I want to achieve is something like:
#Logger
#RestController
#RequestMapping("/api/v1/sample")
class SampleController() {
#GetMapping
fun show(): String {
log.info("SOME LOGGING MESSAGE")
return "OK"
}
}
made this pretty thing yesterday
#Target(AnnotationTarget.CLASS)
#Retention(AnnotationRetention.RUNTIME)
annotation class Log {
companion object {
inline var <reified T> T.log: Logger
get() = LoggerFactory.getLogger(T::class.java)
set(value) {}
}
}
edit:
Don't use this mess above. use
companion object {
private val log: Logger = LoggerFactory.getLogger(this::class.java)
}
see Idiomatic way of logging in Kotlin
Turning Danny Lagrouw's comment into an answer:
You can get a similar to #Slf4j low-boilerplate solution that does not require you to specify the class manually by using Micro Utils' kotlin-logging
import mu.KotlinLogging
private val log = KotlinLogging.logger {}
class LoggingDemoClass() {
fun logSometing() {
log.info("Logging like a pro!")
}
}
Caveat: I've only just started using it but and haven't investigated any of the kotlin sugar it puts on top of Slf4j but so far it seems to handle well enough as a drop-in replacement for #Slf4j in kotlin code.
There is way to provide instance without actual constructor calling.
class ModelImpl #Inject constructor(...): Model{}
#Provides
fun model(inst: ModelImpl): Model = inst
Is there way to do the same if there is no interface? Dagger knows already all dependencies for ModelImpl, so it can create an instance.
This gives obviously dependency cycle:
#Provides
fun model(inst: ModelImpl): ModelImpl = inst
When you use constructor injection Dagger can construct the object for you and you're already using Dagger to create ModelImpl to use it as a binding for Model in your example!
class ModelImpl #Inject constructor(...): Model{}
#Provides
fun model(inst: ModelImpl): Model = inst
// somewhere else...
// both variants would work!
#Inject lateinit var modelImpl : ModelImpl
#Inject lateinit var model : Model
The same would work without the interface
class ModelImpl #Inject constructor(...)
// somewhere else...
#Inject lateinit var model : ModelImpl
If you annotate the constructor then Dagger can create the object for you (if all dependencies can be resolved). This works the same wherever you request the object / dependency,
as a parameter in a #Provides annotated method (as your example)
as a field injection property (#Inject lateinit var)
as a parameter in another objects constructor
as a provision method in a component (fun getFoo() : Foo)
All of the following would work
// foo and bar can both be constructor injected
class Foo #Inject constructor()
class BarImpl #Inject constructor(val foo : Foo) : Bar
#Module
interface BarModule() {
#Binds // you should prefer Binds over Provides if you don't need initialization
// barImpl can be constructor injected, so it can be requested/bound to its interface here
fun bindBar(bar : BarImpl) : Bar
}
#Component(modules = BarModule::class)
interface BarComponent {
fun getBar() : Bar // returns barImpl due to binding
}
#Inject lateinit var bar : BarImpl // but we could as well use the implementation directly
#Inject lateinit var bar : Foo // or just foo
I recommend you to start with a small example, then compile the project and have a look at the generated code. If something is wrong you'll get errors immediately, while you can play around and try different setups!
An addition to David Medenjak anwser. If there is no interface and there is no requirement to group instances into modules, then module could be omitted completely:
class Model #Inject constructor(...){
//...
}
#Component
interface SomeComponent{
fun model(): Model
}
val model = someComponent.model()