Search code examples
androidkotlindaggerdagger-hilt

What should I use for Singleton classes? A real Singleton or @Singleton annotation from Dagger?


I am learning dependency injection with Hilt and Dagger and I want to know... When creating classes with the Singleton pattern, should I use a real Singleton or the Hilt annotation? I searched on the internet but could not find a conclusive solution that shows the difference, also when I click to open the generated file from Hilt it does not looks like a Singleton, is it "threadissuesproof"?

"Real Singleton", like this:

class SongController private constructor() {
companion object {
    @Volatile
    private var INSTANCE: SongController? = null

    fun getInstance(): SongController {
        synchronized(this) {
            var instance = INSTANCE
            if (instance == null) {
                instance = SongController()
                INSTANCE = instance
            }
            return instance
        }
    }
}
}

Or the @Singleton annotation from Hilt?

@Singleton
class SongController @Inject constructor() {}

Is there any benefits of using the annotation or the best and safe option is still the getInstace() -> synchronized() block?

Which one should I use in this case?

And the most important question that I did not understand, will the annotation behave the same way?


Solution

  • In most cases you should prefer the DI framework version of a singleton. It is not exactly the same, but effectively, to your app that uses DI to set everything up, there is only one instance. This is preferred to a traditional singleton, because it will allow you to swap in alternate versions for testing.

    Regarding the comments: object most definitely does not cover the case of a singleton that relies on constructor properties.

    By the way, your traditional singleton code could be improved to use double-checked locking to avoid having to synchronize on every access forever. Actually, the way your code is now, there's no need for Volatile, but you do need it for double-checked locking. Here's an example:

    class SongController private constructor(someParameter: SomeType) {
    companion object {
        @Volatile
        private var INSTANCE: SongController? = null
    
        fun getInstance(someParameter: SomeType): SongController = INSTANCE ?: synchronized(this) {
                INSTANCE ?: SongController(someParameter).also { INSTANCE = it }
            }
    }
    }