Dagger 2 - what is the purpose of a @Singleton annotation class

后端 未结 5 869
既然无缘
既然无缘 2021-02-03 17:43

From the dagger 2 Documentation I noticed that you can have a @Singleton annotated class. What is the purpose of marking a class as @Singleton as I hav

相关标签:
5条回答
  • @Singleton does not really create a Singleton, it is just a Scope, it is advised to not use @Singleton as it is misleading, it gives the impression that we are infact getting a Singleton, but we are not.

    Let's say you annotate your database dependency with @Singleton and link with a Component, now let's say that you initialise this Component in Activities A and B, you will have different instances of your database in your two Activities which is something most people don't want.

    How do you overcome this?

    Initialise your Component once in your Application class and access it statically in other places like Activities or Fragments, now this could soon get out of hand if you have more than 20 Component's as you cannot initialise all of them in your Application class, doing so will also slow down your app launch time.

    The best solution according to me is to create a real Singleton, either double checked or of other variants and use this statically as getInstance() and use this under @Provides in your Module.

    I know it breaks my heart too, but please understand that @Singleton is not really a Singleton, it's a Scope.

    0 讨论(0)
  • 2021-02-03 17:59

    What is singleton?

    Singleton Pattern in android

    A single instance of a class providing a global point of access to itself during the entire application's lifetime.

    @Singleton annotation in Dagger

    A single instance of a class which is unique to a specific component, its access is limited to the scope of the component.

    Purpose of singleton

    To provide a single instance of a class within the dependency graph(component). a component is usually initialized in the application level as it executes only ones for the entire applications lifetime and is accessible by all activities and fragment.

    Let's take an example:

    CoffeeComponent.kt

    @Singleton
    @Component
    interface CoffeeComponent {
    
      fun getCoffeeMaker():CoffeeMaker
      fun inject(activityA: ActivityA)
      fun inject(activityB: ActivityB)
    }
    

    CoffeeMaker.kt

    @Singleton
    class CoffeeMaker @Inject constructor()
    

    CoffeeAplication.kt

    class CoffeeApplication : Application() {
    
      private val component by lazy {
        DaggerCoffeeComponent.builder().build()
      }
    
      fun getAppComponent(): CoffeeComponent = component
    }
    

    Recommended Practise

    Always go for lazy initialization of your component.
    Scenario: say, you are team decided to add an onboarding/tutorial screen or incorporate some other design which doesn't require the component during the initial screens, it will help minimize the startup delay. Always remember, component initialization is expensive.

    ActivityA.kt

    import dagger.Lazy
    
    class ActivityA: AppCompatActivity() {
    
      @Inject
      lateinit var coffeeMaker1:Lazy<CoffeeMaker>
      @Inject
      lateinit var coffeeMaker2:Lazy<CoffeeMaker>
      private val component by lazy {
        (application as CoffeeApplication).getAppComponent()
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btn_activityB.setOnClickListener { startActivity(Intent(this, NewActivity::class.java)) }
    
        component.inject(this)
    
        println("Activity A CoffeeMaker 1 - ${coffeeMaker1.get()}")
        println("Activity A CoffeeMaker 2 - ${coffeeMaker2.get()}")
      }
    }
    


    If your class is expensive to construct, use the dagger's Lazy initialization, please don't confuse it with kotlin's Lazy. You have to import

    import dagger.Lazy

    @Inject
    lateinit var coffeeMaker1:Lazy<CoffeeMaker>
    

    ActivityB.kt

    class ActivityB: AppCompatActivity() {
    
    @Inject
    lateinit var coffeeMaker1:Lazy<CoffeeMaker>
    @Inject
    lateinit var coffeeMaker2:Lazy<CoffeeMaker>
    private val component by lazy { 
    (application as CoffeeApplication).getAppComponent() }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_new)
        component.inject(this)
    
        println("Activity B CoffeeMaker 1 - ${coffeeMaker1.get()}")
        println("Activity B CoffeeMaker 2 - ${coffeeMaker2.get()}")
    }
    }
    

    you will get the Log output as

    Note:

    If you want to share a singleton instance between activities, lazily initialize them in the application level, if you initialize them in an activity you will end up with different instance as the components are different

    0 讨论(0)
  • 2021-02-03 18:01

    @Singleton is inherit @Scope, so in documentation says

    Identifies scope annotations. A scope annotation applies to a class * containing an injectable constructor and governs how the injector reuses * instances of the type. By default, if no scope annotation is present, the * injector creates an instance (by injecting the type's constructor), uses * the instance for one injection, and then forgets it. If a scope annotation * is present, the injector may retain the instance for possible reuse in a * later injection. If multiple threads can access a scoped instance, its * implementation should be thread safe. The implementation of the scope * itself is left up to the injector. <p>In the following example, the scope annotation {@code @Singleton} ensures * that we only have one Log instance: * * <pre> * &#064;Singleton * class Log { * void log(String message) { ... } * }</pre>

    You get the point right? whatever annotation you use or you create a custom one, and they inherit from @Scope it will ensure as singleton.

    0 讨论(0)
  • 2021-02-03 18:10

    Well you can manually create an annotation,which will help to create a singleton object.

    @Scope
    @Retention(RetentionPolicy.CLASS)
    public @interface MyApplicationScope {
    }
    

    When @MyApplicationScope annotation is added with @Provides annotation than it makes dagger to create an object only once and use same object in future. Do remember to add this annotation to the component interface also otherwise you will get the scope related error during compilation.

    If you are using @Singleton annotation then you may end up creating the new objects every time when you will create your component with .build().

    0 讨论(0)
  • @Singleton (and any other scope annotation) makes your class a single instance in your dependencies graph (it means that this instance will be "singleton" as long as Component object exists).

    In short - everytime you're injecting @Singleton annotated class (with @Inject annotation) it will be the same instance as long as you inject it from the same Component.

    For more I'm referring my blog post about how @Singleton and other scopes annotations works in Dagger 2: http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/

    0 讨论(0)
提交回复
热议问题