Dependency Injection - scopes and dependent components with Dagger 2 tutorial
Welcome to lesson 1. Understanding Dagger 2 can be
challenging. I have to admit there is a
lot of boilerplate code. Using Dagger2
for android though is better then other dependency injection (DI) frameworks because it uses no reflection, which is slow on Android.
It’s the best we have right now so lets accept it and jump right into. We will be looking at understanding scoped providers as well as scoped components.
Requirements for this tutorial:
1.
Android Studio
Set up:
Create a empty android activity in Android studio by going
to fileànew
ànew project:
Set the minimum SDK to 16 if you wish or leave it as is.
The rest you can figure out or leave as is and hit finish.
In build.gradle in your app module ensure you have the
following dependencies :
compile 'com.google.dagger:dagger:2.0'
apt 'com.google.dagger:dagger-compiler:2.0'
provided 'org.glassfish:javax.annotation:10.0-b28'
Great, sets up done now onto the juicy stuff.
What does it mean
to scope a component:
In Dagger 2 the scope idea is really about keeping a single
instance of a dependency/class only as long as its scope exists. This avoids a
big memory footprint. Who wants classes hanging around for the lifetime of your
app when there only needed once or twice ? get it ?
That’s right ! if your not using scoped
components your objects are most likely sitting in memory once dagger provided.
Let’s do something about it.
Usually bloggers on this subject work with application scope
vs activity scope. Let us be a bit different approach, it might help us understand scoped
injections easier.
The Story:
Let’s use butter and sandwiches in our example to
follow. There are many kinds of butter.
My favorites are cashew nut butter, almond nut butters not bad either. Sandwiches need butter (to taste good, duh).
So our Sandwiches will depend on a butter.
Once we have a butter we can make a sandwich such as a almond butter
Sandwich or cashew nut butter Sandwich – you get the drift.
My package structure will look like below but for now just
worry about the butter and sandwiches – yummy:
The butter.java class is just
an abstract base class, you can ignore it. But it just simply prints out which
butter we are using:
public abstract class Butter { @Override public String toString() { return getClass().getSimpleName(); } }
The two classes AlmondButter
and CashewButter are empty and just extend from Butter.
AlmondButter.java:
public class AlmondButter extends Butter {}
Notice each package contains
a module; SandwichModule and ButterModule.
It’s just placed here for consistency.
Let’s take a look at the butterModule.java:
@Module
public class ButterModule {
@Provides
@ButterScope
AlmondButter ProvideAlmondButter(){
return new AlmondButter();
}
@Provides
@ButterScope
CashewButter ProvideCashewButter(){
return new CashewButter();
}
}
public class ButterModule {
@Provides
@ButterScope
AlmondButter ProvideAlmondButter(){
return new AlmondButter();
}
@Provides
@ButterScope
CashewButter ProvideCashewButter(){
return new CashewButter();
}
}
All im doing here is telling
Dagger that I want to provide an almondButter or a cashewButter spread. It does nothing else.
Lets see the
sandwichModule.java now:
@Module public class SandwichModule { @Provides @SandwichScope CashewSandwich ProvidesCashewSandwich(CashewButter butter){ return new CashewSandwich(butter); } @Provides @SandwichScope AlmondSandwich ProvidesCashewSandwich(AlmondButter butter){ return new AlmondSandwich(butter); } }
Again, we are just telling
our dagger component that we will be providing the AlmondSandwich or cashewSandwich.
But how do we get the butter
? How does it get passed in ? And what are these two scopes “SandwichScope and
ButterScope” ?
Lets get to the heart of the
discussion, Dagger 2 components.
Components are responsible
for the actual injection. It’s the bridge between modules and the @injection
annotation or command. Components talk
to modules to perform the dependency injections.
Let’s have a look at my
project structure again but this time take a look at the components and scopes:
Custom Scopes and scoped providers
Actually custom scope annotations (like ButterScope and SandwitchScope) in dagger 2 help us to create scope providers!Scope providers are created when we annotate our custom scope onto the provider and onto a component that uses the module the provider is in.
Lets look at ButterScope & SandwichScope respectively:
Butterscope.java:
Scope @Retention(RetentionPolicy.RUNTIME) public @interface ButterScope { }
SandwichScope.java:
@Scope @Retention(RetentionPolicy.RUNTIME) public @interface SandwichScope {}
Just like how dagger uses the
singleton scope we are defining our own here instead. We are creating our own
annotation tag here. When we annotate a provider with custom scope (that matches the scope of the component) we are telling dagger to only create/provide a single instance of that dependent class for the lifetime of the scope.
If we look in SandwichModule.java notice there is a annotation of "SandwichScope" on the provider methods. This means that when i actually call @Inject CashewSandwich and @Inject CashewSandwich2 in a class, it will always inject the same object. If i did not annotate the provider with a scope then calling inject would have injected 2 separate instances of CashewSandwich. So custom scopes are useful for having singletons scoped !
If we look in SandwichModule.java notice there is a annotation of "SandwichScope" on the provider methods. This means that when i actually call @Inject CashewSandwich and @Inject CashewSandwich2 in a class, it will always inject the same object. If i did not annotate the provider with a scope then calling inject would have injected 2 separate instances of CashewSandwich. So custom scopes are useful for having singletons scoped !
Shut up and get to scoped components already !!
Take a look at
ButterComponent.java:
/** remember,Components connect @Modules With @Inject */ @ButterScope @Component(modules={ButterModule.class}) public interface ButterComponent { //expose these for whatever class depends on butter AlmondButter ProvideAlmondButter(); CashewButter ProvideCashewButter(); }
The ButterComponent will be a
dependency for a sandwich. We have to
declare what the butterComponent can provide, here its ProvideAlmondButter and
ProvideCashButter.
A Sandwich component would
depend on the butterComponent:
sandwichComponent.java:
@SandwitchScope @Component(dependencies = ButterComponent.class, modules = SandwichModule.class) public interface SandwichComponent {
CashewSandwich ProvideCashewSandwitch(); void inject (MainActivity mainactivity); }
Notice I declare that
butterComponent.class is a dependency. This
is how in the SandwichModule.java we are able to get the butter provided to us
earlier.
It's important to note that i've also created a inject method here. This will allow our MainActivity class to call the inject method. It allows the MainActivity to use the annotation @Inject. It's daggers way of helping us use the @Inject annotation instead of having to individually reference each dependent item we want. Well see this how this works later on.
Now we’ve figured out how to
make dependent components, lets move forward.
Let’s now go to our
mainActivity and see how we can use all this and create scopes:
public class MainActivity extends AppCompatActivity { private final String TAG = getClass().getSimpleName(); @Inject AlmondButter someAlmondButter; @Inject CashewSandwich sandwich; SandwichComponent sandwichComponent; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /*create the dependent butter for the sandwich here*/ ButterComponent butterComponent=DaggerButterComponent.builder(). butterModule(new ButterModule()).build(); /*create a scope sandwichcomponent here */ sandwichComponent=DaggerSandwichComponent.builder().sandwichModule(new SandwichModule()). butterComponent(butterComponent) .build(); //finally we have a sandwichComponent, lets inject our dependencies sandwichComponent.inject(this); Log.v(TAG,sandwich.toString()); Log.v(TAG,someAlmondButter.toString()); } @Override protected void onDestroy() { super.onDestroy(); //not necessary but it clearly shows the scope being tied to lifecycle of activity sandwichComponent=null; } }
As you can see we build the
sandwichComponent and use butterComponent as a dependent. We store it as a class variable and it will
be garbage collected when the activity is destroyed. This is how you make a scoped graph.
Dagger 2 Lesson 2 @Inject on constructors as providers
Source: github: https://github.com/j2emanue/dagger2_scope_demo
“Creativity is intelligence having fun.” – Albert Einstein
Dagger 2 Lesson 2 @Inject on constructors as providers
Source: github: https://github.com/j2emanue/dagger2_scope_demo
“Creativity is intelligence having fun.” – Albert Einstein
No comments:
Post a Comment