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.
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
- Full example on Github
- Dependency injection with Dagger 2 - the API by froger_mcs
- Dependency injection with Dagger 2 - Introdution to DI by froger_mcs