Inject Kotlin inline class with Dagger/Hilt - kotlin

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)

Related

Where is the defaultDispatche instanced by Hilt?

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

String cannot be provided without an #Provides-annotated method

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).

NullPointerException inside Kodein

I'm trying out Kotlin with Kodein and in my current project I'm getting a NPE inside Kodein and I'm don't know why.
I have some data classes and matching repositories which deliver a list of them:
data class Cat(val name: String)
data class Dog(val name: String)
interface Repository<T> {
val all: List<T>
}
interface CatRepository : Repository<Cat>
interface DogRepository : Repository<Dog>
The implementations of these repositories are currently backed by a master class:
data class AnimalData(val cats: List<Cat>, val dogs: List<Dog>)
I created an abstract base class for the repositories:
abstract class AnimalDataRepository<T>(override val kodein: Kodein) : Repository<T>, KodeinAware {
private val animalData: AnimalData by instance()
abstract val property: (AnimalData) -> List<T>
override val all: List<T> = animalData.let(property)
}
So that the repository implementations look like this:
class CatRepositoryImpl(override val kodein: Kodein) : CatRepository, AnimalDataRepository<Cat>(kodein) {
override val property = AnimalData::cats
}
Setting this up and running with:
fun main() {
val kodein = Kodein {
bind<AnimalData>() with singleton { AnimalData(listOf(Cat("Tigger")), listOf(Dog("Rover"))) }
bind<CatRepository>() with singleton { CatRepositoryImpl(kodein) }
}
val catRepository: CatRepository by kodein.instance()
println(catRepository.all)
}
leads to a NPE inside Kotlin:
Exception in thread "main" java.lang.NullPointerException
at org.kodein.di.KodeinAwareKt$Instance$1.invoke(KodeinAware.kt:176)
at org.kodein.di.KodeinAwareKt$Instance$1.invoke(KodeinAware.kt)
at org.kodein.di.KodeinProperty$provideDelegate$1.invoke(properties.kt:42)
at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
at AnimalDataRepository.getAnimalData(KodeinExample.kt)
at AnimalDataRepository.<init>(KodeinExample.kt:27)
at CatRepositoryImpl.<init>(KodeinExample.kt:30)
at KodeinExampleKt$main$kodein$1$2.invoke(KodeinExample.kt:40)
at KodeinExampleKt$main$kodein$1$2.invoke(KodeinExample.kt)
at org.kodein.di.bindings.Singleton$getFactory$1$1$1.invoke(standardBindings.kt:130)
...
I'm not clear why this happens. It has something to do with the use of the "property" mapping lamba in AnimalDataRepository, because when I don't use that it works fine.
Complete code as gist: https://gist.github.com/RoToRa/65d664d2d7497ddbf851a1be019f631d
this is because in your class AnimalDataRepository you have defined:
override val all: List<T> = animalData.let(property)
While Kodein is working lazily, thus all is defined before, that's why animalData is null. However, you can fix this by doing:
override val all: List<T> by lazy { animalData.let(property) }

Not able to inject a multi-binding with kotlin and dagger

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.

Using android-extensions`s Parcelize annotation on objects that extends Parceable sealed classes

I'm using kotlin android extensions to auto generate my Parcelables, but given the following code I'm getting 'Parcelable' should be a class.
the code:
sealed class Action : Parcelable
#Parcelize
object Run : Action()
#Parcelize
data class Ask(
val question: String
) : Action()
My understanding is that it is impossible to use #Parcelize in an object (Once it is working on the Ask class) in the way I'm doing it.
I want to use the Parcelable annotation in an object that extends a sealed class, so I'm do not know how to do it and do not write the following boilerplate.
object Run : Action() {
override fun writeToParcel(p0: Parcel?, p1: Int) {}
override fun describeContents() = 0
#JvmField
val CREATOR = object : Parcelable.Creator<Run> {
override fun createFromParcel(parcel: Parcel) = Run
override fun newArray(size: Int) = arrayOfNulls<Run?>(size)
}
}
You need to make sure that you have you Kotlin up to date.
You can follow an example here.