Tag

Dagger2

Browsing

In this blog, I’m going to explain dependency injection using Dagger2. We learn uses and implementation of Dagger2. In this Dagger2 android example, we are explaining how dagger2 provides solution to tight coupling problem.

Overview

In the following article, We are explaining comprehend used of Dependency Injection. Dependency injection provides an allegiant solution to tight coupling problem. The way it is actually implemented in code had fundamental level is to program against an interface rather than concurrent implementation. If a java class creates an instance of another class via the new operator, then it cannot be used and tested independently from that class and is called a hard dependency.

  // Base structure
    public interface Data{
        List getData();
        void saveData();
    }

    // Low level class implementation
    public class DB implements Data{

        @Override
        public List getData() {
            // querying to data
            return null;
        }

        @Override
        public void saveData() {
           // saving to DB
        }
    }

Understand hard dependency

You can see, we have defined an interface called data, In that interface, we have two methods getData() and saveData(). We call these methods we don’t need to worry about where the data comes from. We have simply using the interface to request the data. It is upon the low-level class like network class to implement the data interface and get our data with this in a mind implementation detail can change. But we can only change the code from low-level classes than implement the interface.

So we have established here that is to better interfaces rather than concurrent classes. For this toward effectively we need to dependency are able to get what the need at runtime. One of the major advantages of dependency injection is that make testing very easy.

   public class OfferManager {
        
        public OfferManager() {
            buyOneGetOne = OfferFactory.getBuyOneGetOne();
        }
        private BuyOneGetOne buyOneGetOne;
        public OfferManager(BuyOneGetOne buyOneGetOne) {
            this.buyOneGetOne = buyOneGetOne;
        }
    }

Suppose you have an object which does something like this init constructor which is to get an object instance by calling a factory. This can be troublesome some when all we want to do is run unit test on our class called OfferManager, especially if the getOneBuyOne something does network access.

So Now, We are looking at mocking getOneBuyOne object but also somehow also intercepting factory call, this is quite awkward or cumbersome instead we pass the object an argument to the constructor. Now we have the problem elsewhere but testing can become a lot easier. To run a test you just create a demo offer object and pass that in. The constructor will look now little bit like test which except argument of offer object.

Solve hard dependency problem

Any application that composed many object that collaborates with each other to perform some useful stuff. Traditionally each object is responsible for obtaining to own references to the dependent object. This lead to highly couple classes and hard to test code.

 //Without Dependency Injection (DI)
    public class Offer {
        BuyOneGetOne buyOneGetOne = new BuyOneGetOneItme1();
        SpecialOffer specialOffer = new SpecialOffer();
        BlackFridayOffer blackFridayOffer = new BlackFridayOffer();
        //rest implementation
    }

    //With Dependency Injection (DI)
    public class Offer{
        BuyOneGetOne buyOneGetOne; /* [Inject the instance of BuyOneGetOneItem1 at runtime]  */
        SpecialOffer specialOffer; /* [Inject the instance of SpecialOffer at runtime] */
        BlackFridayOffer blackFridayOffer; /* [Inject the instance of BlackFridayOffer at runtime]  */
    }

For example, let’s consider the Offer object, Offer depends on condition and number of items to run. The class here is Offer and buyOneGetOne is instantiated by calling a new BuyOneGetOneItme1( ). Here is the Offer object is responsible for creating a dependent object. But what if you want to change the type of dependent object after the initial buyOneGetOne, We need to recreate the buyOneGetOne object with new dependency say BuyOneGetOneItme2, but only the original code author can do it

Then what does Dependency Injection do for us? When using Dependency Injection object are given there dependencies are runtime rather than compile time. So that now we can change the offer object whenever we want. here the SpecialOffer object injected at runtime.

Dagger2

Dagger2 is a dependency injection framework. It provides an allegiant solution to tight coupling problem. Dagger2 based on the Java Specification Request. It uses code generation based on annotations.

Dagger 2 uses the following annotations

  • Provider:- classes with annotated with @Module is responsible for providing an object which can be injected.
  • Consumer:- The @Inject annotation is used to defining a dependency
  • Connector:- A @Component annotated interface defines the connection between the provider and consumer.

Dagger2 Sample Application

Now we will demonstrate how to implement dependency injection using Dagger2 in Android. You have to follow the following steps to setup dependency injection in own project.

Add dependency in build.gradle

We will open the Android Studio and will open app module build.gradle add implementation in dependencies and just click on sync now.

    implementation 'com.google.dagger:dagger-android:2.20'
    implementation 'com.google.dagger:dagger-android-support:2.20' 
    // if you use the support libraries
    annotationProcessor 'com.google.dagger:dagger-android-processor:2.20'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.20'

Create an application module class for Dagger

After gradle has finished building everything we are ready to create some classes which are needed to Dagger2 work. First, we create an application module class named is AppModule add below code

package com.wave.dagger;

import android.app.Application;
import android.content.Context;

import javax.inject.Singleton;

import dagger.Module;
import dagger.Provides;

/**
 * Created on : Feb 09, 2019
 * Author     : AndroidWave
 */
@Module
public class AppModule {

    private Application application;

    public AppModule(Application application) {
        this.application = application;
    }

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

}

This is where dagger keep will keep track of the dependencies. It must be annotated with @Module. So dagger knows this is a module. Later on, we will create module every feature we build. In such module Dagger will look for variable methods and possible instance provider.

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

The methods that exposed the available return type should also be annotated with @Provides decorators. The @Singleton annotation also signals to Dagger compiler that the instance should be created only once in the application. For example, we are specifying the context that uses singleton annotations that can be part of the dependency list.

Create Application Component for Dagger

We need to create an application component where dagger knows where to inject dependency to. In Dagger2 injected class called components. These components assign references in our activity, services, and fragments to have access to the singleton which we are already defined.

We need to annotate this class with app @Component decoration and set the module which defined earlier. the Activities, Services, and Fragment added here in this interface like below code

In src folder just create an interface with named is AppComponent and add below code

package com.wave.dagger;

import javax.inject.Singleton;

import dagger.Component;
/**
 * Created on : Feb 09, 2019
 * Author     : AndroidWave
 */
@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {

    void inject(MainActivity target);
}

Define dagger in Application

We need to define an application object, where dagger will live through-out the entire life spends in the application. We should do all this job within the application class since the instead declared only once. Just create a class that extends application components and add below code.

package com.wave.dagger;

import android.app.Application;

/**
 * Created on : Feb 09, 2019
 * Author     : AndroidWave
 */

public class App extends Application {

    private AppComponent component;

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

        //needs to run once to generate it
        component = DaggerAppComponent.builder()
                .appModule(new AppModule(this))
                .build();

    }


    public AppComponent getComponent() {
        return component;
    }

}

You are seeing we will overriding onCreate methods here, where will be the component will be instantiated by dagger. We also defined public getComponent() methods which will return AppComponent instance. You notice these methods is highlighted with red with some compilation error.

So In dagger, we have to define every module, we have in app. Later on, we may have app module or other feature modules. right now we have only app module. You have to set application name in AndroidManifest for initializing dagger components.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.wave.dagger">

    <application
        android:name=".App"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        tools:ignore="GoogleAppIndexingWarning">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

This way our application used this class to handle initial initialization

Now we can inject into activity a context.

Now Open the MainActivity and add below code. After that rebuild the project and import necessary class

package com.wave.dagger;

import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

import javax.inject.Inject;

/**
 * Created on : Feb 09, 2019
 * Author     : AndroidWave
 */

public class MainActivity extends AppCompatActivity {

    @Inject
    Context context;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ((App) getApplication()).getComponent().inject(this);


    }
}

As you see in an onCreate() methods we need to define this injection, so that dagger components interface we will be used. Notice we are cast getApplication() to our App class. So now we get dagger component and inject activity.

We saw how we can use a Dagger2 framework to implement dependency injection into an Android application.

Download Sample Project- Dagger2 Android Example

I recommend read MVP architect android apps with Dagger2, Retrofit & RxJava

In this article, I’m going to explain the implementation of MVP architecture using Dagger 2, ButterKnife, Room Persistence, Rxjava 2, RxAndroid, Retrofit, logging and debugging. In this Android tutorial, we will build project that contains Architect Android Apps with MVP, Dagger, Retrofit & Rxjava.

I will share repository that contains all above library implementation following best practices. We create superior design in such way that it could be inherited and maximize the code reuse.

To order to build a clean code in the MVP architecture, some tool and frameworks are needed. These are Dagger2, Retrofit, RxJava, RoomPersisstense It will be good to understand how each of these libraries plays a specific part in the role to build better code. There almost industries standard of build a modern application for Android.

Prerequisite

As this article we are using many libraries, So you need to have knowledge of each one library. because in this sample app all library integrated with depends on one another. I recommend you got through MVP Architect Sample Application Tutorials Series.

MVP Architect Sample Application Tutorials Series

  1. MVP :- https://androidwave.com/android-mvp-architecture-for-beginners-demo-app/
  2. Dagger2 :- https://androidwave.com/dagger2-android-example/
  3. RxJava2:- https://androidwave.com/rxjava-rxandroid-tutorials/
  4. Retrofit:- https://square.github.io/retrofit/
  5. Room Persistence:- https://androidwave.com/working-with-room-persistence-library/
  6. Debugging and Logging:- https://androidwave.com/useful-tools-for-logging-debugging-in-android/
  7. Downloadable Font:- https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
  8. Globally Error Handling – https://androidwave.com/retrofit-globally-error-handling/

The app has following packages:

  1. data: It contains all the data accessing and manipulating components.
  2. Room Persistence (Local Database )
  3. Networking stuff (REST APIs call)
  4. Shared Preferences.
  5. : Dependency providing classes using Dagger 2.
    1. Components
    2. Module  
  6. ui: View classes along with their corresponding Presenters.
  7. Views
  8. Custom Components
  9. Activity, fragment
  10. : Services for the application.
    1. All service and Job IntentService should be placed here.
  11. utils: Utility classes.
    1. All utility class write in this package
MVP architecture using Dagger 2

How to use this repo

Project Setup

Go to the root directory of workspace where you want place own project and just take a clone from GitHub repo using below command. If you face any issue while taking clone, Read this article

$ git clone https://github.com/droidwave/MVP-Architect-Android-Apps.git

After taking successful clone you have to do following changes

Brief Intro of Repo

In this repo, we have created a separate package for each module or functionality. For all user interface part is placed in UI package. Inside the UI package, we have created separate package for each small app module or activity. I would like to suggest just create a new package for new activity. For example for login module create login, for main activity create main, for profile create a profile package. such as below diagram.

Follow below step for create a new activity
  • Just create a new package name like profile. Now go to file menu and create a new activity named is ProfileActivity.
  • Now extends the BaseActivity instance of AppCompatActivity and override setUp() methods.
package com.androidwave.cleancode.ui.profile;


import android.os.Bundle;

import com.androidwave.cleancode.R;
import com.androidwave.cleancode.ui.base.BaseActivity;

public class ProfileActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_profile);
    }

    @Override
    protected void setUp() {

    }
}
  • Create a new interface name is ProfileMvpView which extends MvpView and declare all UI operation methods here such as updateProfile(UserProfile profile) for as per need
package com.androidwave.cleancode.ui.profile;

import com.androidwave.cleancode.data.network.pojo.UserProfile;
import com.androidwave.cleancode.ui.base.MvpView;

/**
 * Created on : Feb 17, 2019
 */
public interface ProfileMvpView extends MvpView {
    void updateProfile(UserProfile profile);
}
  • Furthermore, create a new interface with names PofileMvpPresenter<V extends ProfileMvpView> which extends MvpPresenter<V>. The view is here type of View. Here you declare all data related methods. For example network operation, fetching data from local DB etc.
package com.androidwave.cleancode.ui.profile;

import com.androidwave.cleancode.ui.base.MvpPresenter;

/**
 * Created on : Feb 17, 2019
 */
public interface ProfileMvpPresenter<V extends ProfileMvpView> extends MvpPresenter<V> {
    void onViewPrepared();
}
  • Create a new presenter with name ProfilePresenter which extends BasePresenter and implementing PofileMvpPresenter. After that override super constructor and annotated with @Inject annotations like below
  • Now open ProfileActivity and implement ProfileMvpView.
package com.androidwave.cleancode.ui.profile;

import com.androidwave.cleancode.data.DataManager;
import com.androidwave.cleancode.ui.base.BasePresenter;
import com.androidwave.cleancode.utils.rx.SchedulerProvider;

import io.reactivex.disposables.CompositeDisposable;

/**
 * Created on : Feb 17, 2019
 * Author     : AndroidWave
 */
public class ProfilePresenter<V extends ProfileMvpView> extends BasePresenter<V>
        implements ProfileMvpPresenter<V> {

    public ProfilePresenter(DataManager manager, SchedulerProvider schedulerProvider, CompositeDisposable compositeDisposable) {
        super(manager, schedulerProvider, compositeDisposable);
    }

    @Override
    public void onViewPrepared() {
        getMvpView().showLoading();
        getCompositeDisposable().add(getDataManager()
                .getUserProfile(String.valueOf(getDataManager().getUserId()))
                .subscribeOn(getSchedulerProvider().io())
                .observeOn(getSchedulerProvider().ui())
                .subscribe(response -> {
                    if (!isViewAttached()) {
                        return;
                    }
                    getMvpView().hideLoading();
                    /**
                     * Update view here
                     */
                    getMvpView().updateProfile(response.getData());
                }, error -> {
                    if (!isViewAttached()) {
                        return;
                    }
                    getMvpView().hideLoading();

                    /**
                     * manage all kind of error in single place
                     */
                    handleApiError(error);
                }));
    }
}
  • After that, you have to write a DI code for providing presenter here > base package => di =>module => ActivityModule
    @Provides
    @PerActivity
    ProfileMvpPresenter<ProfileMvpView> provideProfilePresenter(ProfilePresenter<ProfileMvpView> presenter) {
        return presenter;
    }
  • Finally, Open ProfileActivity and attached presenter with view
package com.androidwave.cleancode.ui.profile;


import android.os.Bundle;

import com.androidwave.cleancode.R;
import com.androidwave.cleancode.data.network.pojo.UserProfile;
import com.androidwave.cleancode.ui.base.BaseActivity;

import javax.inject.Inject;

public class ProfileActivity extends BaseActivity implements ProfileMvpView {
    @Inject
    ProfileMvpPresenter<ProfileMvpView> mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_profile);
        getActivityComponent().inject(this);
        mPresenter.onAttach(ProfileActivity.this);
        setUp();
    }

    @Override
    protected void setUp() {

    }

    @Override
    public void updateProfile(UserProfile profile) {

    }
}
  • Open ActivityComponent from di =>components => ActivityComponent and add bellow code
  void inject(ProfileActivity profileActivity);

Finally, Your activity is ready to run, Almost same step you have to follow for fragment.

How to add RestApi

We have to follow some basic steps to add RestAPIs in this repos, as you seeing BaseDataManager class is responsible for handle all type data such as Rest APIs, PreferencesManager and local DB also.

  • Just navigate to base package=> data => network => NetworkService and add retrofit calling methods
package com.androidwave.cleancode.data.network;

import com.androidwave.cleancode.data.network.pojo.FeedItem;
import com.androidwave.cleancode.data.network.pojo.LoginRequest;
import com.androidwave.cleancode.data.network.pojo.UserProfile;
import com.androidwave.cleancode.data.network.pojo.WrapperResponse;

import java.util.List;

import io.reactivex.Single;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Path;

/**
 * Created on : Jan 19, 2019
 * Author     : AndroidWave
 */
public interface NetworkService {
    /**
     * @return Observable feed response
     */
    @GET("feed.json")
    Single<WrapperResponse<List<FeedItem>>> getFeedList();


    @POST("login")
    Single<WrapperResponse<UserProfile>> doLoginApiCall(@Body LoginRequest mRequest);

   // here is api that fetching user details    
    @GET("user/profile/{user_id}")
    Single<WrapperResponse<UserProfile>> getUserProfile(@Path("user_id") String userId);
}
  • Now open RestAPI manager and one methods for dealing getProfile API
package com.androidwave.cleancode.data.network;

import com.androidwave.cleancode.data.network.pojo.FeedItem;
import com.androidwave.cleancode.data.network.pojo.LoginRequest;
import com.androidwave.cleancode.data.network.pojo.UserProfile;
import com.androidwave.cleancode.data.network.pojo.WrapperResponse;

import java.util.List;

import io.reactivex.Single;

public interface RestApiHelper {

    Single<WrapperResponse<UserProfile>> doLoginApiCall(LoginRequest request);

    Single<WrapperResponse<List<FeedItem>>> getFeedList();

    // add this line of code
    Single<WrapperResponse<UserProfile>> getUserProfile(String userId);
}
  • Now override this method in all subclass. Open RestApiManager and add below line
    @Override
    public Single<WrapperResponse<UserProfile>> getUserProfile(String userId) {
        return mService.getUserProfile(userId);
    }
  • Finally, Open base BaseDataManager and add an unimplemented method like below
   @Override
    public Single<WrapperResponse<UserProfile>> getUserProfile(String userId) {
        return mApiHelper.getUserProfile(userId);
    }
Download Architect Android Apps with MVP, Dagger, Retrofit & Rxjava