Posts

About

Clean Android Networking Code Part 3 - Dependency Injection

July 21, 2015

Introduction

In Part 2 a way of unit testing networking code was introduced using Espresso. To setup the unit test for the activity, the WebRequestQueue had to be mocked out to ensure no networking calls would be made. In reality, the activity does not know about WebRequestQueue therefore the unit test violates the Law of Demeter. To fix this it would be nice if instead of mocking WebRequestQueue, the GoogleStringRequest could be mocked instead. To accomplish this, the mock of GoogleStringRequest would have to be injected into the activity. This is no easy task due to the complexity of the Android activity lifecycle. However, it is much easier to do with the help of a dependency injection framework such as Dagger 2.

activity lifecycle

Dagger 2

The point of Dagger is to have a framework capable of injecting dependencies into an object. This separates the object and the responsibility of creating the dependencies. It also increases testability by allowing the injections of mocked objects. The following annotations make up the basics of Dagger.

The @Inject annotation describes what should be injected.

The @Module annotation signifies classes that will provide the dependencies to be injected. A module class has methods that are marked with @Provides. These methods create the dependencies.

The @Component annotation describes a component that fits everything together. A component describes what classes can have dependencies injected into them and what modules are used to provide those dependencies.

Setting up Dagger 2

Including Dagger 2 into a project

To include Dagger 2 in a project the following must be added.

App’s gradle file

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

Project’s gradle file

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

dependencies {
    compile "com.google.dagger:dagger:2.0"
    apt "com.google.dagger:dagger-compiler:2.0"
    provided 'org.glassfish:javax.annotation:10.0-b28'
}

Creating modules

As stated before the goal is to be able to inject GoogleStringRequest into the activity. To start out with, a module must be created to provide this dependency. A new RequestModule class can be created made up of one method that provides a GoogleStringRequest. A GoogleStringRequest needs a Context for its constructor. This can be passed into the RequestModule. To make things easier the tag parameter was moved from the constructor into to the makeRequest method.

@Module
public class RequestModule {
    private Context context;

    RequestModule(Context context) {
        this.context = context;
    }

    @Provides
    GoogleStringRequest provideGoogleStringRequest() {
        return new GoogleStringRequest(context);
    }
}

Creating a component

Now a component is needed to hold onto the module and to tell what to inject it into. To help specify what can be injected an IComponent interface can be created like the one below.

public interface IComponent {
    public void inject(MainActivity mainActivity);
}

Creating a base application to hold the component

The component that will be created needs to be accessible to the activity and any others that are created. For this reason, a base application can be created to hold onto the component, application wide. Another interface can be created that extends IComponent that will specify the modules to be used. Since this interface now holds all the information needed it can be annotated with @Component. There will only be one instance of this component so it can also be annotated with @Singleton. Now the component can finally be built using a Dagger builder. This Dagger builder generated and is called Dagger<name of the class>_<name of component>.builder().

public class BaseApplication extends Application {
    @Singleton
    @Component(modules = {RequestModule.class})
    public interface ApplicationComponent extends IComponent {
    }

    private IComponent component = null;

    @Override public void onCreate() {
        super.onCreate();
        component = DaggerBaseApplication_ApplicationComponent.builder()
                .requestModule(new RequestModule(this))
                .build();
    }

    public IComponent component() {
        return component;
    }
}

To use the new BaseApplication, it must be specified in the application section to manifest.

AndroidManifest.xml

<application
        ...
        android:name="BaseApplication">

Injecting

The BaseApplication now holds the component that can be injected into the activity. To do this, the activity must get the component from BaseApplication and then use it to inject itself. After doing this it will be able to inject a GoogleStringRequest.

public class MainActivity extends AppCompatActivity {
    @Inject
    GoogleStringRequest googleStringRequest;

    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ....
        ((BaseApplication) getApplication()).component().inject(this);
        googleStringRequest.makeRequest(REQUEST_TAG);
    }

}

Modifying the unit test

The goal was to be able to inject a mock GoogleStringRequest into the main activity. With a few more additions and modifications this can be accomplished. First another module must be created to provide the mock. Next a new component must be created using the new module. Finally the component used in the BaseApplication must be set to the new component.

The following must be added to the gradle file to use dagger for tests.

dependencies {
    androidTestCompile "com.google.dexmaker:dexmaker:1.2"
    androidTestApt "com.google.dagger:dagger-compiler:2.0"
}

MockRequestModule that provides a mocked GoogleStingRequest that will do nothing instead of making the request.

@Module
public class MockRequestModule {

    @Provides
    GoogleStringRequest provideGoogleStringRequest() {
        GoogleStringRequest request = mock(GoogleStringRequest.class);
        doNothing().when(request).makeRequest(anyString());
        return request;
    }
}

Changes to BaseApplication to allow setting a component.

@Override public void onCreate() {
    super.onCreate();
    if (component == null) {
        component = DaggerBaseApplication_ApplicationComponent.builder()
                .requestModule(new RequestModule(this))
                .build();
    }
}

public void setComponent(IComponent component) {
    this.component = component;
}

Changes to the unit test to create and set the new component MainActivityUnitTest.java

@Singleton
@Component(modules = {MockRequestModule.class})
public interface TestComponent extends IComponent {
}

@Before
public void setUp() {
    Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
    BaseApplication app = (BaseApplication) instrumentation.getTargetContext().getApplicationContext();
    TestComponent component = DaggerMainActivityUnitTest_TestComponent.builder()
            .mockRequestModule(new MockRequestModule())
            .build();
    app.setComponent(component);
    eventBusHelper = new EventBusHelper(this);
    launchActivity(new Intent());
}

References


Written by Jacob Oakes
I am a software architect who enjoys learning new things, clean code, and automated tests.