28
votes

After spending a ludicrous amount of time trying to figure out why my dagger injections weren't working; I realised that the "object" type in Kotlin was the problem.

The following did not work, the injected "property" was null.

object SomeSingleton {

    @Inject
    lateinit var property: Property

    init {
        DaggerGraphController.inject(this)
    }
}

However, the following DID work just fine:

class NotSingleton {

    @Inject
    lateinit var property: Property

    init {
        DaggerGraphController.inject(this)
    }
}

I tried google, I tried the documentation but I could not pin point the reason behind this. Also note that I havent tried this with JAVA, JAVA doesnt have the concept of singletons built in anyway.

Why is this the case? Why is a kotlin singleton unable to inject members but a regular non-singleton class can?

4
Did you find any solution?André Sousa
Sounds like object.init runs sooner than the initialization of your DaggerGraphController thing.EpicPandaForce

4 Answers

15
votes

If you look into kotlin bytecode you'll find that the code you've written is translated into following:

public final class SomeSingleton {
    public static LProperty; property // <- Notice static field here

    public final getProperty()LProperty
    ...

    public final setProperty(LProperty)V
    ...
}

As you can see the actual field is static which makes it uneligible for instance injection. You may try to move @Inject annotation onto setter method by doing so:

object SomeSingleton {
    @set:Inject
    lateinit var property: Property
    ...
}
6
votes

I tried to use dagger.Lazy<YourClass> and it works

 @set:Inject
lateinit var authClient: dagger.Lazy<PlatformAuthClient>
5
votes

A workaround for this can be to extend a BaseClass that consists of the fields to be injected.

object SomeSingleton : BaseClass {
    ...
    ...
}

open class BaseClass{
    @Inject
    lateinit var property: Property

    init{
        YourDaggerComponent.inject(this)
    }

}

This does work, although this would leak this, which comes up as an android studio warning, to get rid of that make the Base class abstract and instead inject the fields in your original object class

object SomeSingleton : BaseClass {
    ...
    ... 
 // Add the init block here instead of the base class
 init{
        YourDaggerComponent.inject(this)
    }
}

abstract class BaseClass{
    @Inject
    lateinit var property: Property
    
   //Remove the init block from here

}

And you Dagger AppComponent interface can be like, any of those function def should work

interface Component{
    fun inject(someSingleton : SomeSingleton)
    //OR
    fun inject(baseClass: BaseClass)
}

I hope this helps....

0
votes

You may still need the @Singleton decorator on top of your object definition. That decorator doesn't make your class 'singleton', it's just used by Dagger to get all the dependencies in the same spot.