Dagger Scopes —Let’s explain it simply

Kacper Hreniak
5 min readMar 18, 2019

--

Let’s start with another article about Dagger. I assume that you are a user of Dagger 2 library, so I’d like to focus on Dagger Scopes without any special introduction to Dagger 2. I will try to present it in a very simple way and give you only necessary information required to explain Scopes in Dagger 2. At the end of this article, I’ll present a typical example of use. Let’s make some scopes!

Photo by Adi Goldstein on Unsplash

Add dependecies to a project

// Add Dagger dependencies
dependencies {
implementation 'com.google.dagger:dagger:2.x'
kapt 'com.google.dagger:dagger-compiler:2.x'
}

Create a scope

A scope is an annotations class with specific additional annotations: @Scope and @Retention. @Scope annotation is provided by Dagger library to define custom scopes. In our example, we create two scopes: @ActivityScope (for activities) and @FragmentScope (for fragments).

import javax.inject.Scope@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class ActivityScope
@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class FragmentScope

Providers

As you know, Dagger uses providers to create instances of required objects. We need to annotate providers by @Provides to let Dagger 2 know which method to use as a provider.

@Provides
fun provideBar(): Bar = Bar()

In this case, Dagger creates a new instance of Bar each time when Bar object is requested. We can add scope annotation, for example, @Singleton to provide a single instance of a created object. @Singleton is a predefined scope by Dagger library. It’s a very useful part of the library which can be used to annotate other providers in modules of other libraries which aren’t created by you.

@Singleton
@Provides
fun provideBar(): Bar = Bar()

Modules — time to provide an instance

Providers are grouped into modules. Module class has to be annotated by @Module . It’s a good pattern to aggregate providers by their use in a common module. It could be a feature module or networking module.

import dagger.Module
import dagger.Provides
@Module
class ExampleModule {
@Singleton
@Provides
fun provideFoo(): Foo = Foo()
}

Components — Let`s connect it together — — sprawdzić

In Dagger ther are two kinds of Component: Component and Subcomponent.

Subcomponents are components that inherit and extend the object graph of a parent component. You can use them to partition your application’s object graph into subgraphs either to encapsulate different parts of your application from each other or to use more than one scope within a component.

For Scopes, Subcomponent is very important to break an object graph into small, single responsibility subgraphs. Each subcomponent could be annotated by another scope. So we can manage a lifecycle of our objects. Scoped object lives as long as its component and it can be seen as a Singleton in Subcomponent context.

Scoped object lives as long as its component it can be seen as Singleton in Subcomponent context.

@ContributeAndoridInjector

If you think that you are not using a subcomponent let me explain to you how @ContributeAndroidInjector annotation works. This annotation was introduced in Released 2.11 and under the hood, it creates subcomponents per each Fragment, Activity or Service. This feature made it so that you don’t have to care about creating components or adding it to a parent component — everything is done out of the box. Subcomponents are creating and adding to dependencies graphs by invoking methods: AndroidInjection.inject(this) or AndroidSupportInjection.inject(this) .

@ContributeAndoridInjector(modules = [FragmentModule::class])
fun bindFeatureFragment(): FeatureFragment

@ContributeAndroidInjector generates SubComponent under the hood

According to the Definition of the Subcomponent I quoted it can be annotated by a scope to manage the lifecycle of the available object. The Provider of the object inside the component can be unscoped so It means that each invocation creates a new instance or can be scopped — an instance of object lives as long as the component.

So when will the Component be destroyed? Each call of AndroidInjection.inject(this) or AndroidSupportInjection.inject(this) creates a new (sub)component. This means that this part of the dependencies tree is recreated and a new instance of the object will be available.

Example

ActivityBuilderModule is the module connected with AppComponent and its responsibility is creating an injector for Activities in the application. In this example, it binds a single Activity with @ActivityScope annotation. The generated subcomponent consists of ActivityModule and other modules available from its parent component — in our example AppComponent .

import dagger.Module
// required imports
@Module
class ActivityBuilderModule{
@ActivityScope
@ContributeAndoridInjector(modules = [ActivityModule::class])
fun bindFeatureActivity(): FeatureActivity
}

ActivityModule holds all required providers for our generated subcomponent for FeatureActivity. This module includes FragmentBuilderModule which is required to create injectors and subcomponents for Fragments. It consists of two providers:

  • providePresenter() — each time it generates a new instance of the presenter
  • provideFoo() — by using a @ActivityScope once created instance lives as long as a component — until you invoke AndroidInjection.inject(this) in this Activity.
import dagger.Module
import dagger.Provides
@Module(include = [FragmentBuilderModule::class]
class ActivityModule {
@Provides
fun providePresenter():
FeatureActivityPresenter = FeatureActivityPresenter()
@ActivityScope
@Provides
fun provideFoo(): Foo = Foo()
}

FragmentBuilderModule responsibility is creating an injector and a subcomponent for fragment. Generated subcomponent has @FragmentScope annotation and his parent component is a subcomponent generated for FeatureActivity .

import dagger.Module
// required imports
@Module
class FragmentBuilderModule {
@FragmentScope
@ContributeAndoridInjector(modules = [FragmentModule::class])
fun bindFeatureFragment(): FeatureFragment
}

FragmenModule consists of two providers for FeatureFragment :

  • providePresenter(foo: Foo, bar: Bar) — it`s scoped by @FragmentScope , so as long the Subcomponent for FeatureFragment exists it will provide the same instance of the presenter
  • provideBar() — each time it generates a new instance of Bar
import dagger.Module
import dagger.Provides
@Module
class FragmentModule {
@FragmentScope
@Provides
fun providePresenter(foo: Foo, bar: Bar):
FeatureFragmentPresenter = FeatureFragmentPresenter(foo)
@Provides
fun provideBar(): Bar = Bar()
}

AppComponent is an explicitly creating Component which consists of a single module: ActivityBuilderModule and method inject(application: Application) to injecting data inside the Application.

@Component(modules = [ActivityBuilderModule::class)
public interface AppComponent{
fun inject(application: Application);}

It`s a perfect moment to inject required objects into Fragment and Activity.

import android.app.Activity
import android.os.Bundle
import javax.inject.Inject
class FeatureActivity : Activity() {

@Inject
lateinit var featureActivityPresenter: FeatureActivityPresenter

override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
}
}
import android.app.Fragment
import android.content.Context
import javax.inject.Inject
class FeatureFragment : Fragment() { @Inject
lateinit var featureFragmentPresenter: FeatureFragmentPresenter
override fun onAttach(context: Context?) {
AndroidSupportInjection.inject(this)
super.onAttach(context)
}
}

Summary

If you need to scope your provider you need to annotate the provider and the component with the same scope annotation — predefined @Singleton or custom @ActivityScope, @FragmentScope

@ContributeAndoridInjector under the hood generates a subcomponent connected to its module component with included modules.

If you keep a structure of modules presented in an example your fragment component is connected with modules for fragment, activity and application. Activity component consists of modules for activity and application.

That`s it. Scopes is a very simple and powerful tool. If you understand dependencies, connections and responsibilities between components, modules and scopes your project will be as simple as possible.

Please Comment! I will be happy to help you (or answer any questions)!

--

--

Kacper Hreniak

Senior Mobile Software Engineer — Android platform, Clean code, Data Structure and Algorithm, Blockchain