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();

    }
}

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 !


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

No comments:

Post a Comment