Dagger Scopes —Let’s explain it simply
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!
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.
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 presenterprovideFoo()
— by using a @ActivityScope once created instance lives as long as a component — until you invokeAndroidInjection.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 forFeatureFragment
exists it will provide the same instance of the presenterprovideBar()
— 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.Injectclass 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.Injectclass 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)!