Android Sleepers
Tuesday, 22 December 2015
Sunday, 20 December 2015
Lesson 1 - Dagger2 -Dagger 2 scoped modules and components
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
Tuesday, 8 December 2015
Lesson 2 -dagger2 - Dagger 2 provider object with @Inject annotation
Dagger2 – Providing
Object’s by tagging Constructor with @Inject tutorial
Welcome to lesson 2. Let’s continue on using
dagger2 by using our lesson 1 example of butter and sandwiches in http://j2emanue.blogspot.com/
In Dagger2 we can provide
dependencies using modules registered in components. This much we know from
lesson 1. Dagger2 also allows for
providing an object by tagging its constructor with @Inject.
Let’s take our story from
lesson 1 further and say now we want to add a utensil to the story. A knife for our spread. So Let’s create a knife class. My directory
structure will now look like this after creating a utensils package:
The Knife.java class itself
will look like this:
public class Knife { int count=0; @Inject public Knife(){ System.out.println("a spreading knife has been created"); }; @Override public String toString() { return "Knife{" + "count=" + ++count + '}'; } }
What I’m about to show you is
how we can inject knife into our mainActivity without putting it in any module.
We wont reference it in any component either.
Placing @Inject on a constructor
makes it detectable to Dagger2. Notice that our knife constructor is annotated
with @Inject. It becomes automatically
unscoped but ready to be injected into our mainactivity. Let’s see what our
mainActivity would look like now that we have a knife:
Actually we have two knife classes which are injected. Once we have a component build in onCreate(), those classes become available to us.public class MainActivity extends AppCompatActivity { private final String TAG = getClass().getSimpleName(); //@Inject //AlmondButter someAlmondButter; @Inject CashewSandwich sandwich; @Inject CashewSandwich sandwich2; @Inject Knife mKnife; @Inject Knife mKnife2; 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, " first:" + sandwich.toString()); Log.v(TAG, "second:" + sandwich2.toString()); Log.v(TAG, "first knife:" + mKnife.toString()); Log.v(TAG, "second knife" + mKnife2.toString()); } }
Keep in mind, the knife class
is unscoped right now. That means, each time we call @inject to supply us with
a knife, we are creating a brand new object. If we wanted to keep the same
object we would use the scope of the component (in our case @SandwichScope) and
mark the class (NOT the constructor) with this scope. Lets see how we can make
our knife.java class scoped:
@SandwitchScope public class Knife { int count=0; @Inject public Knife(){ System.out.println("a spreading knife has been created"); }; @Override public String toString() { return "Knife{" + "count=" + ++count + '}'; } }
Notice the top line has a
scope annotation. This makes our knife class be a singleton within the
sandwichScope. Since our component in mainactivity
is a sandwichComponent tagged with @SandwichScope, the knife object becomes a
singleton for as long as this component exists.
I hope this information was
informative.
In conclusion I would think modules and components to provide objects is a more cleaner way of organizing your project then marking a constructor with @Inject. It would be difficult to know a class is provided easily otherwise. Why ? You would have to search your code base under each class and find out which one has a constructor with @Inject. Thats a code smell. With modules we can simply search the module file to find all providers (especially if modules are package specific).
In conclusion I would think modules and components to provide objects is a more cleaner way of organizing your project then marking a constructor with @Inject. It would be difficult to know a class is provided easily otherwise. Why ? You would have to search your code base under each class and find out which one has a constructor with @Inject. Thats a code smell. With modules we can simply search the module file to find all providers (especially if modules are package specific).
If I have seen further than
others, it is by standing upon the shoulders of giants. - Isaac Newton
Tuesday, 1 December 2015
Gradle Dependency scopes - Lesson 2
Gradle dependency scopes for Android
A bit of history on why i chose this topic. I ran into an issue with the android 65k limit (your code can have a max of 65k methods). Thought of using proguard to strip unused methods but it slowed down debug builds. Developers were left waiting a long time just compile and run. Tried multiDex and it slows down your build (at least in previous android gradle schemes). MultiDex might work for you but i'd like to demo how we can lower method counts before having to go into that direction.
The best approach to keep the method count down would be to not even have the methods in the first place. Don't use unnecessary methods, right ? But how ? I'll keep this as short as possible and you can get the drift and carry on.
Dependency types
- Provided
- Compile
- Package
- Apt (for android apt plugin)
Provided:
Have you ever looked in a gradle.build file for android and seen the word provided as part of the depenencies. Like this:
This means that we only need the dependency for compilation. The end user does not need it, so drop it after we compile. This can save us some unnecessary methods.
Compile:
You've seen this one all the time as its the most used. It tells gradle we need this library all the time, at compile and run time. Keep it.
Package:
This would be for libraries you want only when packaging.
Apt:
As a side note, the android-apt plugin also has a scope we can use called apt. You might have seen it before when defining dagger dependencies for example. It looks like this in gradle dependencies closure:
apt 'com.google.dagger:dagger-compiler:2.0'
provided 'org.glassfish:javax.annotation:10.0-b28'
What is this apt scope ? Its very similar to provide actually. In most cases it's the same effect. One prime difference is when you use the provided scope, the generated code is not available to the IDE. For example, let's change the dagger compiler to have a provided scope:
provided 'com.google.dagger:dagger-compiler:2.0'
provided 'org.glassfish:javax.annotation:10.0-b28'
Now if we sync gradle, the IDE will not be able to find your dagger component (ie. daggers boilerplate generated code) and you'll get an error of something like this at compile time:
error: cannot find symbol
return DaggerMyComponent.builder()
Using apt allows the IDE to see the generated code.
Conclusion:
If you find yourself looking for a way to minimize method calls or creating a library, have this strategy in mind at design time. Your users will likely appreicate the consideration.
Truth is ever to be found in simplicity, and not in the multiplicity and confusion of things. - Isaac Newton
Subscribe to:
Posts (Atom)