Android Android Dependency Injection using Dagger2
Post
Cancel

Android Dependency Injection using Dagger2

1 - What is Dependency Injection?

Dependency Injection is a design pattern which help us to reduce the coupling between modules (classes). Hence, that makes our modules reusable, interchangeable and easy to test.

For example, in our Android app (more specifically, in LoginActivity), we must hit the authentication endpoint via AuthenticationApi to verify if user information is correct.

1.1 - Not using DI

This is the AuthenticationApi class, which will contact the server and verify input credentials:

public interface IAuthenticationApi {

    Future<Authentication> authentication(String username, String password);

}

public class AuthenticationApi implements IAuthenticationApi{

    @Override
    public Future<Authentication> authentication(String username, String password) {
        // Do a lot of stuff here like hitting server with provided auth info
        return Observable.just(true);
    }

}

Then, inside LoginActivity, we instantiate new AuthenticationApi in onCreate() method.

public class LoginActivity extends Activity implements OnClickListener{

    private AuthenticationApi mAuthenticationApi;

    @Override
    public void onCreate() {
        super.onCreate();
        //Init view and do other stuffs
        mAuthenticationApi = new AuthenticationApi();
    }

    public void onClick(View view) {
        mAuthenticationApi.authentication(mEtUsername.getText(), mPassword.gettext());
    }

}

This cause a strong couple between LoginActivity and AuthenticationApi which is not really good. Like:

  • What if we want to add a Logger in IAuthenticationApi when running debug but not release version?
  • What if we want IAuthenticationApi return mock data when running test?
  • What if in demo version, we want IAuthenticationApi retrieves data from local database instead of server?

This is where Dependency Injection jump into the picture!

1.2 - Using DI

In Dependency Injection pattern, we usually have a Factory class which provides all the dependencies for the module/application.

public class ApiFactory {

    public IAuthenticationApi getAuthenticationApi() {
        return new AuthenticationApi();
    }

}

Then, inside Application class, we create a new instance of ApiFactory and expose public method to refer to. By this way, we can access the ApiFactory throughough entire application:

public class MyApplication extends Application {

    private ApiFactory mApiFactory;

    public void setApiFactory(ApiFactory apiFactory) {
        mApiFactory = apiFactory;
    }


    public ApiFactory getApiFactory() {
        return mApiFactory;
    }

}

Inside LoginActivity, we can easily refer to ApiFactory to get the IAuthenticationApi object:

public class LoginActivity extends Activity implements OnClickListener{

    private IAuthenticationApi mAuthenticationApi;

    @Override
    public void onCreate() {
        super.onCreate();
        //Init view and do other stuffs
        mAuthenticationApi = ((MyApplication) getApplication()).getApiFactory().getAuthenticationApi();
    }

    @Override
    public void onClick(View view) {
        mAuthenticationApi.authentication(mEtUsername.getText(), mPassword.gettext());
    }

}

As you can see, by using DI, now the LoginActivity does not depend on the AuthenticationApi anymore! Basically, LoginActivity doesn’t know the concrete implementation of IAuthenticationApi, the type of IAuthenticationApi was decided by the ApiFactory.

By having multiple ApiFactory based on build type, you can easily decide the concrete implementation of IAuthenticationApi in each case.

1.3 - Conclusion

After 2 examples above, DI is quite simple, right?! But if DI is simple, why do we need a framework?

Basically, the technique of DI is simple. But if your project is quite big, you have so many modules depend on each others, the dependencies will become more complicated. And it also produce a lot of boilerplate code which is not so beautiful!

2 - Dagger2 Introduction

2.1 - What is Dagger2?

Dagger2 is a fully static, compile-time dependency injection framework for both Java and Android. It is an adaptation of an earlier version created by Square and now maintained by Google.

2.2 - Why using Dagger2?

Traceability

  • Easy to navigate around the entire project using Find Usages… and Open Declaration
  • Trace through the code that clear and well structured

Clarify API

  • Simpler @Module declarations
  • Injections will fail at runtime if not correctly configured, which helps you find mistakes faster

Performance

  • No performance hit because injections are not performed using relfection like other dependency injection frameworks

3 - How to use Dagger2?

3.1 - Set Up

Add following plugin and dependencies into app scope build.gradle file:

apply plugin: 'com.neenbedankt.android-apt'

buildscript {
  repositories {
    jcenter()
  }
  dependencies {
    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
  }
}

android {
  ...
}

dependencies {
  apt 'com.google.dagger:dagger-compiler:2.0'
  compile 'com.google.dagger:dagger:2.0'
  provided 'javax.annotation:jsr250-api:1.0'

  ...
}

3.2 - Define Module class

Module classes is where you define all the dependencies provider for your application. You can have many Module classes in your project.

For example, below is the Android Module class which provides all the main Android objects so you can easily inject later:

@Module
public class AndroidModule {

    @Provides
    @Singleton
    Context provideAppContext() {
        return DemoDagger2Application.getInstance().getApplicationContext();
    }

    @Provides
    SharedPreferences provideDefaultSharedPreferences(final Context context) {
        return PreferenceManager.getDefaultSharedPreferences(context);
    }

    @Provides
    AccountManager provideAccountManager(final Context context) {
        return AccountManager.get(context);
    }

    @Provides
    NotificationManager provideNotificationManager(final Context context) {
        return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    }

}

Or another example below - ApiModule, which provides all Api related object for the application:

@Module
public class ApiModule {

    @Provides
    @Singleton
    AuthenticationApi provideAuthenticationApi() {
        return new AuthenticationApi();
    }

}

When you declare a @Singleton annotation, Dagger2 will manage all the concurrency and make sure return the same object everytime for you. You don’t need to care about that anymore! Cool huh?

3.3 - Declare Components

Component interfaces is where you declare the association between Module and the Injection Target. In other words, the Component interfaces told Dagger2 that dependencies in specific Module will be used in listed classes.

@Singleton
@Component(modules = {AndroidModule.class, ApiModule.class})
public interface MyComponent {
    void inject(LoginActivity activity);

     public final static class Initializer {
        public static MyComponent init(boolean mockMode) {
            return DaggerMyComponent.builder()
                .androidModule(new AndroidModule())
                .apiModule(new ApiModule())
                .build();
        }
    }
}

By the way, in case you didn’t notice, the DaggerMyComponent was auto generated by Dagger2 after you build the project. And all the method like androidModule() was also auto generated based on your Module class name.

3.4 - Instantiate the Component in Application class

Next, inside your application class, instantiate the Component you just declare above:

public class MyApplication extends Application {

    private static MyApplication mInstance;
    private MyComponent mComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this;
        mComponent = mComponent = MyComponent.Initializer.init(true);   
    }

    public static MyApplication getInstance() {
        return mInstance;
    }

    public MyComponent getComponent() {
        return mComponent;
    }

}

3.5 - Injection

Now you can inject the dependency that you need inside any classes you declared in the Component by simply using the annotation @Inject. And before using any Injected instance, remember to call the inject() method of Component:

DemoDagger2Application.getInstance().getComponent().inject(this);

Sample code:

public class LoginActivity extends Activity implements OnClickListener{

    @Inject
    protected AuthenticationApi mAuthenticationApi;

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

        MyApplication.getInstance().getComponent().inject(this);

    }

    @Override
    public void onClick(View view) {
        mAuthenticationApi.authentication(mEtUsername.getText(), mPassword.gettext());
    }

}

4 - Instrumentation Testing with Dagger, Mockito, and Espresso

In instrumentation test, we usually mock the server response to make sure the test suite can cover every cases from succeed to fail.

In this part of the post, I’ll show you how to write instrumentation tests with Dagger, Mockito and Espresso.

Beside the official ApiModule which will be applied for release build, we also need a DebugApiModule for the debug build. This module will decide to return the real or mock object base on the mock mode:

@Module
public class DebugApiModule {

    private boolean mIsMockMode;

    public DebugApiModule(boolean isMockMode) {
        mIsMockMode = isMockMode;
    }

    @Provides
    @Singleton
    AuthenticationApi provideAuthenticationApi() {
        if(mIsMockMode) {
            return Mockito.mock(AuthenticationApi.class);
        } else {
            return new AuthenticationApi();
        }
    }

}

Under the debug code base directory, we create another MyComponent class:

@Singleton
@Component(modules = {AndroidModule.class, DebugApiModule.class})
public interface MyComponent {
     void inject(LoginActivity activity);

     public final static class Initializer {
        public static MyComponent init(boolean mockMode) {
            return DaggerMyComponent.builder()
                .androidModule(new AndroidModule())
                .debugApiModule(new DebugApiModule(mockMode))
                .build();
        }
    }
}

You can notice that, instead of using ApiModule we will you DebugApiModule this time.

Then, we modify the Application class to expose a method to set the mock mode:

public class MyApplication extends Application {

    private static MyApplication mInstance;
    private MyComponent mComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this;
        mComponent = MyComponent.Initializer.init(true);
    }

    public static MyApplication getInstance() {
        return mInstance;
    }

    public MyComponent getComponent() {
        return mComponent;
    }

    public void setMockMode(boolean useMock) {
        mComponent = MyComponent.Initializer.init(false);
    }

}

Now, in test class (below is the code of BaseActivityTest which will be extended later), we can easily switching between mock and real mode:

public class BaseActivityTest {

    @Inject
    AuthenticationApi mMockAuthenticationApi;

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        MyApplication app = (MyApplication) getInstrumentation().getTargetContext().getApplicationContext();
        app.setMockMode(true);
        app.getComponent().inject(this);
    }

    @Override
    protected void tearDown() throws Exception {
        App.getInstance().setMockMode(false);
    }

}

5 - Sample Code

So, that’s all for Dagger2 in this post.

You can find the full sample source code on my Github page.

In later posts, I’ll cover some advance topics of Dagger2.

Hope you enjoy it! :D

This post is licensed under CC BY 4.0 by the author.