Click here to Skip to main content
15,891,253 members
Articles / Mobile Apps / Android

Learn Android Dependency Injection with Dagger 2

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
20 Apr 2016CPOL10 min read 21.8K   3   4
Learn Android Dependency injection with Dagger 2

Lately, it is hard to browse through Android tutorials without an article on Dagger 2 or MVP staring at you. In this post, I will share with you a practical definition and example of Dagger 2 and in an upcoming post, I will share with you a to the point example of MVP.

What is Dependency Injection

At a basic level, Dependency Injection encourages flexibility. The term was coined by Martin Fowler in an article about Inversion of Control pattern. A simple example of Dependency Injection is recipes – take a recipe for coffee for example. You can define the ingredients for a cup of coffee like this:

  1. 6 table spoons of dark coffee
  2. 1 cup of water
  3. 1 table spoon of Splenda (a type of sweetener) and
  4. 1 teaspoon of vanilla flavored cream

This recipe can work in most cases, however if someone prefers a caramel flavored cream, a new set of recipe has to be made and if someone prefers a different type of sweetener likewise a new recipe has to be made because the original recipe was defined with concrete ingredients. We can however update our coffee recipe with generic ingredients like this:

  1. 6 table spoons of coffee
  2. 1 cup of water
  3. 1 table spoon of sweetener
  4. 1 teaspoon of cream

This now makes our recipe flexible, the actual type of cream or sugar used will be determined or rather injected at the time the coffee is made. This gives the flexibility to, for example, use mock ingredients during training and switch to real ingredients during production.

The power of dependency injection shines with (sticking with the coffee analogy) if you now want to make cappuccino which requires or depends on some coffee input. The recipe for the cappuccino can be based on the flexible coffee recipe.

Yet another special drink can also be made which requires or depends on cappuccino. So that means three layers of dependencies. With Dependency Injection pattern, the generic ingredients, will be replaced (or injected) with concrete ingredients by the bartender just in time before serving the coffee.

How Does This Apply to Android Development

This applies to Android development in two ways: one, we as developers drink lots of coffee on an average and two, Android is a complex web of dependencies. The code we write often depends on libraries which has dependencies on other libraries.

Context and SharedPreference are good examples of this as we will see shortly in the example that comes with this tutorial. Without Dependency Injection, you are limited to using SharedPreference in only files that inherit from Context aka Activity and Fragments because new instance of SharedPreference has a dependency on Context.

Without getting into MVP in this post, I can say that it makes life easy if you extract your application logic including data persistence from Fragments and Activities into POJO classes aka Presenters. Since these POJO classes do not have Context, how do you get access to SharedPreference which has a dependency on Context? The answer is Dependency Injection – specifically we use Dagger 2 to inject SharedPreference to any class that needs it as you will see in the example that follows. So let us take a look at Dagger 2 next.

What Exactly does Dagger 2 Do

Before we look at what exactly Dagger 2 does, I will like to say that the good thing about Dagger 2 is that it is not magical. Everything it does, at-least the part that you are concerned with is prescriptive. That means you prescribe to Dagger 2 what goes where via annotations. So what does Dagger 2 do? It is a factory that creates and manages your dependency for you based on your specification.

Going back to the coffee analogy, Dagger 2 can be likened to a vending machine that stands near the bartender and creates new copies of the concrete ingredient needed by the bartender to make coffees based on the checklist provided to it.

The checklist must be clear, for example if it specifies that when the recipe calls for 1 table spoon of cream, it should also say what should be the concrete implementation that should be created to satisfy that requirement. Dagger 2 does not make the decision of what object to use to satisfy a dependency for you, that is why I said that it is not magical. You have to specify that upfront. Let’s see this in an example.

Dagger 2 By Example

In this example, we are going to create a Java class file called ShoppingCart.java, this file is used in an Android shopping cart app. Part of the work of this ShoppingCart class file is to remember the items that the users add the shopping cart in case there is a configuration change aka phone call came in before they complete their shopping session by checking out. In the web, there are cookies where this kind of temp information can be stored but in Android we have SharedPreference for such a time like this.

Since this ShoppingCart.java file is a humble plain old Java object (POJO), we need to use Dagger 2 to inject SharedPreference to it. Follow the steps below to complete the tutorial. If you are feeling fired up about Dagger 2 and want to see other practical examples, you may want to be notified about my upcoming course Pronto SQLite.

Step 1 – New Project Creation

  1. Create a brand new Android, you can also add this to an existing Android Studio project if you choose to. I am going to name my project ProntoShopApp and select the defaults and click finish.
  2. At the root of the project, create the following class files:
    1. ShoppingCart.java – We have talked about this class already, so it should be obvious what is its mission. Next is
    2. ProductPresenter.java – yet another mention of Presenter, don’t worry I will cover MVP in another post, but just know that it is nothing but a Java class file.
    3. At the root of the project, create a package called dagger and add the following class files to the package
      1. AppComponent.javaComponent is one of the three legs upon which Dagger 2 stands, this is simply an interface that does two things:
        1. It lists the Dagger Modules in this app.
        2. It lists injectable targets, for example, we need SharedPreference in our ShoppingCart.java class, so it is an injectable object that we must specify in the AppComponent.
      2. AppModule.javaModule is another one of the three legs that Dagger 2 stands upon. This is a standard Java class that will be decorated with Dagger 2 specific annotations and it is the methods in this class that provide the dependencies. This particular module provides the Context.
      3. ShoppingCartModule.java – This class is another module that will provide – you guessed it instances of ShoppingCart.
  3. At the root of the application, add a package called model and add the following class files to this package.
    1. Product.java – This defines the products that the app will sell
    2. LineItem.java – This extends product to add price
  4. At the root of the project, add file called ProntoShopApplication.java – this will extend from Application.
  5. Update the name of the Application in Manifest.xml to reflect the name of the class you just added ProntoShopApplication.java
  6. The source code for this sample project can be found here.
  7. Share – If you have found this post useful, please share with someone who can benefit from it.

Step 2 – Add Dagger 2

  1. In your Project build.gradle, add the following line:
    Java
    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    This will help Android Studio to recognize Dagger 2 generated code. Your Project gradle file should now look like this:
    Java
    // Top-level build file where you can add configuration options common to all sub-projects/modules.
    
    buildscript {
        repositories {
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:1.5.0'
            classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
        }
    }
    
    allprojects {
        repositories {
            jcenter()
        }
    }
    
    task clean(type: Delete) {
        delete rootProject.buildDir
    }
  2. In your module build.gradle file, add the highlighted lines of code:
    Java
    apply plugin: 'com.android.application'
    apply plugin: 'com.neenbedankt.android-apt'
    android {
        compileSdkVersion 23
        buildToolsVersion "23.0.2"
    
        defaultConfig {
            applicationId "com.okason.prontoshopapp"
            minSdkVersion 16
            targetSdkVersion 23
            versionCode 1
            versionName "1.0"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        testCompile 'junit:junit:4.12'
        compile 'com.android.support:appcompat-v7:23.2.1'
        compile 'com.android.support:design:23.2.1'
        apt 'com.google.dagger:dagger-compiler:2.2'
        compile 'com.google.dagger:dagger:2.2'
        provided 'javax.annotation:jsr250-api:1.0'
        compile 'com.google.code.gson:gson:2.6.2'
    }
  3. Build your project.
  4. Remember to update your applicationId in case you just copied the above line of code and paste.

Step 3: Update Models

  1. Update Product.java with the following code and then use Android Studio Code->Generate->Getter and Setter to generate the boiler plate getter/setter code.
    Java
    private long id;
        private String productName;
        private String description;   
        private double salePrice;
    
        public Product(){
        }
    
        public Product(Product product){
            this.id = product.getId();
            this.productName = product.getProductName();
            this.description = product.getDescription();
            this.salePrice = product.getSalePrice();       
    
        }
  2. Update LineItem.java with the following code and then also use Android Studio to generate the required boiler plate getter/setter code.
    Java
    public class LineItem extends Product {
        private int quantity;
    
        public LineItem() {
        }
    
        public LineItem(Product product, int quantity) {
            super(product);
            this.quantity = quantity;
        }
    
        public int getQuantity() {
            return quantity;
        }
    
        public void setQuantity(int quantity) {
            this.quantity = quantity;
        }
    
        public double getSumPrice() {
            return getSalePrice() * quantity;
        }
    }

Step 4: AppModule Class

Update your AppModule.java class with the code below. We are now beginning to leverage Dagger 2. Make sure that your ProntoShopApplication.java extends the Application class. Notice the @Module annotation at the top of the class. That is what signifies to Dagger 2 that this is a Module. And what do Modules do again, you may ask. Modules in Dagger 2 contain methods which are essentially advanced switch statements. So here we are saying, case Context: return the Application class. It is best practices to name those method names that begin with provide.

Java
@Module
public class AppModule {
    private final ProntoShopApplication app;

    public AppModule(ProntoShopApplication app) {
        this.app = app;
    }

    @Provides @Singleton
    public Context provideContext() {
        return app;
    }
}

Step 5: Update the ShoppingCart Module

Same with the AppModule.java, we are annotating this class with @Module annotation. We are saying wherever in our application that we need the ShoppingCart, create a new instance of the ShoppingCart and since the ShoppingCart has a dependency on SharedPreference, go ahead and satisfy that dependency by asking the PreferenceManager. And since the PreferenceManager has a dependency on Context before it can fulfill the request for a SharedPreference, Dagger 2 now knows to go to our AppModule and satisfy that dependency by returning our Application class.

Java
@Module
public class ShoppingCartModule {

    @Provides @Singleton
    SharedPreferences providesSharedPreference(Context context){
        return PreferenceManager.getDefaultSharedPreferences(context);
    }

    @Provides @Singleton
    ShoppingCart providesShoppingCart(SharedPreferences preferences){
        return  new ShoppingCart(preferences);
    }
}

Don’t worry about the red lines in Shopping Cart, we will address that shortly.

Step 6: Update the Injector Class

Modules are potential dependency revolvers, however before they can be deployed, Dagger 2 needs to know which objects are inject-able targets and it needs to get a list of Modules that are providing dependency solutions. We provide these two information in the AppComponent.java class like so: Notice that it has the list of our two modules and the classes that we may need a dependency to be injected.

Java
@Singleton
@Component(
        modules = {
                AppModule.class,
                ShoppingCartModule.class
        }
)
public interface AppComponent {
    void inject(ProductListener presenter);    
    void inject(MainActivity activity);

}

Step 7: Update the Shopping Class

Your Cart logic could be as complex as your business needs demands, here is a sample implementation that demonstrates saving the cart content to the SharedPreference.

Java
public class ShoppingCart {
    private final SharedPreferences sharedPreferences;
    private SharedPreferences.Editor editor;
    private List<LineItem> shoppingCart;

    private static final String OPEN_CART_EXITS = "open_cart_exists";
    private static final String SERIALIZED_CART_ITEMS = "serialized_cart_items";
    private static final String SERIALIZED_CUSTOMER = "serialized_customer";

    public ShoppingCart(SharedPreferences sharedPreferences) {
        this.sharedPreferences = sharedPreferences;
        editor = sharedPreferences.edit();
        initShoppingCart();
    }

    private void initShoppingCart() {
        shoppingCart = new ArrayList<>();

        Gson gson = new Gson();

        if (sharedPreferences.getBoolean(OPEN_CART_EXITS, false)){
            String serializedCartItems = sharedPreferences.getString(SERIALIZED_CART_ITEMS,"");
            String serializedCustomer = sharedPreferences.getString(SERIALIZED_CUSTOMER,"");
            if (!serializedCartItems.equals("")){
                shoppingCart = gson.<ArrayList<LineItem>>fromJson(serializedCartItems,
                        new TypeToken<ArrayList<LineItem>>(){}.getType());
            }
        }
        updateApp();
    }

    public void addItemToCart(LineItem item){
        if (shoppingCart.contains(item)){
            int currentPosition = shoppingCart.indexOf(item);
            LineItem itemAlreadyInCart = shoppingCart.get(currentPosition);
            itemAlreadyInCart.setQuantity(itemAlreadyInCart.getQuantity() + item.getQuantity());
            shoppingCart.set(currentPosition, itemAlreadyInCart);
        }else {
            shoppingCart.add(item);
        }
    }

    public void clearShoppingCart(){
        shoppingCart.clear();
        editor.putString(SERIALIZED_CART_ITEMS, "").commit();
        editor.putString(SERIALIZED_CUSTOMER, "").commit();
        editor.putBoolean(OPEN_CART_EXITS, false).commit();
        updateApp();
    }

    public void removeItemFromCart(LineItem item){
        shoppingCart.remove(item);
        updateApp();
    }

    public void completeCheckout(){
        shoppingCart.clear();
        updateApp();
    }

    private void updateApp() {
       //perform any action that is needed to update the app
    }

    public List<LineItem> getShoppingCart() {
        return shoppingCart;
    }

    public void saveCartToPreference(){
        if (shoppingCart != null) {
            Gson gson = new Gson();
            String serializedItems = gson.toJson(shoppingCart);
            editor.putString(SERIALIZED_CART_ITEMS, serializedItems).commit();
            editor.putBoolean(OPEN_CART_EXITS, true).commit();
        }
    }

    public void updateItemQty(LineItem item, int qty) {

        boolean itemAlreadyInCart = shoppingCart.contains(item);

        if (itemAlreadyInCart) {
            int position = shoppingCart.indexOf(item);
            LineItem itemInCart = shoppingCart.get(position);
            itemInCart.setQuantity(qty);
            shoppingCart.set(position, itemInCart);
        } else {
            item.setQuantity(qty);
            shoppingCart.add(item);
        }

        updateApp();
    }
}

Step 8: Update the Application Class

This is the last and very important step, if you copy the code below and paste into your application class, you will get compiler error. Why? Because the DaggerComponent has not been created. You remember that our AppComponent is nothing but an Interface, and you remember that the power of an interface lies in the implementation class. Dagger creates the concrete implementation using the @ annotations that we supplied and adds the prefix to Dagger to them so our AppComponent which is an interface will now have a concrete implementation called DaggerAppComponent.

Update your Application class like so:

Java
public class ProntoShopApplication extends Application {

        private static ProntoShopApplication instance = new ProntoShopApplication();
        private static AppComponent appComponent;

        public static ProntoShopApplication getInstance() {
            return instance;
        }

        @Override
        public void onCreate() {
            super.onCreate();
            getAppComponent();
        }

        public AppComponent getAppComponent() {
            if (appComponent == null){
                appComponent = DaggerAppComponent.builder()
                        .appModule(new AppModule(this))
                        .build();
            }
            return appComponent;
        }
}

Step 9 – Put Dagger 2 to Good Use

Now that we have everything wired up, we can actually use Dagger 2 like this:

Java
public class ProductListener {

    //We are creating a class member variable for the 
    //Shopping cart that we will be injecting to this class
    @Inject
    ShoppingCart mCart;
    
    public ProductListener(){
        //Here is where the actual injection takes place
        ProntoShopApplication.getInstance().getAppComponent().inject(this);
    }

    //Here is an example of how we are using the injected shopping cart
    public void onItemQuantityChanged(LineItem item, int qty) {
        mCart.updateItemQty(item, qty);      
    }

    //Another example of using the shopping cart
    public void onAddToCartButtonClicked(Product product) {
        //perform add to checkout button here
        LineItem item = new LineItem(product, 1);
        mCart.addItemToCart(item);
    }

    public void onClearButtonClicked() {
        mCart.clearShoppingCart();
    }

    public void onDeleteItemButtonClicked(LineItem item) {
        mCart.removeItemFromCart(item);       
    }
}

Conclusion

There you have a it, a practical introduction to Dagger 2 dependency injection framework. This is barely scratching the surface on the immense possibilities that this tool provides. The post should help you get started using Dagger and you will get clarity as you pound the keyboard because code answereth all things.

If you have found value in this post, please use the social media buttons to share this post as it may benefit another Android developer. The source code for this sample project can be found here.

The post Learn Android Dependency Injection with Dagger 2 appeared first on Val Okafor.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) ValOkafor.com
United States United States
My name is Val Okafor, I am a Senior Software Engineer with specialization in Android Development. Learning and problem solving is my passion and I share my Android development knowledge through my blog ValOkafor.com.

My Android courses are the courses I wish I had when I started. I teach Android development in the context of a fully developed Android app. I believe that new Android concepts are better understood if they are presented in the context of creating an app from scratch to finish.

I focus on creating Productivity Android apps and besides Android development I have 7 years’ experience as System Administrator supporting Enterprise applications and 2 years’ experience as a Web Developer building websites using PHP and ASP.Net.

I have worked for corporations such as The Home Depot, American Council on Exercise, Legend3D and HD Supply, Inc. I have a bachelor's degree in Information Technology from National University San Diego, California and a master's degree in Software Engineering from Regis University Denver, Colorado.

I enjoy sharing my extensive work experience through my blog, social media.

Comments and Discussions

 
QuestionStatic variables under ProntoShopApplication Pin
Alexandr Berdnikov20-Sep-16 7:19
Alexandr Berdnikov20-Sep-16 7:19 
QuestionIlluminating examples Pin
Member 124992802-May-16 21:21
Member 124992802-May-16 21:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.