Dagger is greate for dependency injection. Dependency injection is greate to test parts of a system. So lets use dagger (dagger2 since it supports android better).
Following we have a look on how a dagger2 can be integrated into to application without getting annoying. For further readings, checkout the offical dagger2 pages or following blog helpful posts. I wont be explaining how it actually works here, but more provide a getting started guide, to have a functioning dagger2 application:
include dagger2 dependencies
Add following to the build.gradle file:
ext {
// ...
daggerVersion = "2.25.2"
}
dependencies {
// ...
implementation "com.google.dagger:dagger:${project.daggerVersion}"
implementation "com.google.dagger:dagger-android:${project.daggerVersion}"
annotationProcessor "com.google.dagger:dagger-android-processor:${project.daggerVersion}"
annotationProcessor "com.google.dagger:dagger-compiler:${project.daggerVersion}"
androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:${project.daggerVersion}"
(1)
androidTestAnnotationProcessor "com.google.dagger:dagger-android-processor:${project.daggerVersion}"
(1)
}
<1> Enable access of the Annotation Processor in the Insturmented tests. Needed to be able to provide mocked versions
After adding the dependencies, be sure to sync project with the gradle files in the android studio. |
Recommended gradle changes
tweaking the app/build.gradle as following can improve to code:
android {
// ...
compileOptions { (1)
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
testOptions {
unitTests.includeAndroidResources = true (2)
unitTests.returnDefaultValues = true (3)
}
}
<1> Enable using Java 1.8 features like lambdas, highly recommended. It will increase the workflow and helping improving the writing style. <2> helps with the unit tests, checkout the https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.TestOptions.UnitTestOptions.html#com.android.build.gradle.internal.dsl.TestOptions.UnitTestOptions:includeAndroidResources[documentaiton] <3> prevents errors, but can also end up with NullPointer exceptions while Android.Log is used. to prevent those, have a look at the https://github.com/bjoernQ/unmock-plugin[unMock plugin]
Setup ActivityModule
First add an module which provides the activity. It will enable injecting the default MainActivity.
package ch.amk.exercise4.mqtt;
import dagger.Module;
import dagger.android.ContributesAndroidInjector;
@Module (1)
public interface CoreActivityModule { (2)
@ContributesAndroidInjector(modules = { })
abstract MainActivity contributeFeedbackFormActivityAndroidInjector(); (3)
}
1 | define it as a module |
2 | make it an interface |
3 | provide the Activity class through an abstract function. The function name can be choosen freely but it needs to have the @ContributesAndroidInjector annotation. |
Setup Core component
Dagger2 creates a dependency tree. This tree has a root object called component, which includes all subtree dependencies. leaves of the dependency tree are modules und branches are subcomponents.
To be able to create the tree, the first thing we need is the root component, create it as following:
package ch.amk.exercise4.mqtt;
import android.app.Application;
import dagger.BindsInstance;
import dagger.Component;
import dagger.android.AndroidInjectionModule;
import dagger.android.AndroidInjector;
@Component(modules = {
AndroidInjectionModule.class, (1)
CoreActivityModule.class, (2)
})
public interface CoreComponent extends AndroidInjector<CoreApplication> {
@Component.Builder (3)
interface Builder {
@BindsInstance
Builder application(Application application);
CoreComponent build();
}
}
1 | Include the AndroidInjectionModule , it provides the DispatchingAndroidInjector<Object> used by the CoreApplication |
2 | Include dependent Module. Any Module providing implementations can be set here. |
3 | The builder is needed, so in every Module, the Application context can be injected. This is really helpful and therefor great to have always present. |
Setup CoreApplication
Lets override the Android base Application to create the Dagger dependency tree on startup.
package ch.amk.exercise4.mqtt;
import android.app.Application;
import javax.inject.Inject;
import dagger.android.AndroidInjector;
import dagger.android.DispatchingAndroidInjector;
import dagger.android.HasAndroidInjector;
public class CoreApplication extends Application implements HasAndroidInjector {
@Inject
DispatchingAndroidInjector<Object> dispatchingAndroidInjector; (1)
@Override
public void onCreate() {
super.onCreate();
this.createComponent(); (2)
}
protected void createComponent() {
DaggerCoreComponent.builder() (3)
.application(this).build()
.inject(this); (4)
}
@Override
public AndroidInjector<Object> androidInjector() {
return this.dispatchingAndroidInjector; (5)
}
}
1 | inject the injector for android |
2 | create the component (use a separate function to make it more simple to override it in the instrumented test environment) |
3 | Build CoreComponent (This might throw an error till the project has build successfully) |
4 | Inject itself, this will resolve all class attributes with the @Inject annotation |
5 | return the injector used by the android components |
MainActivity
Since android does not support dagger2 out of the box, we have to call an injection function. This can be done as following:
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputEditText;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttSecurityException;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import javax.inject.Inject;
import javax.inject.Named;
import ch.amk.exercise4.mqtt.client.MqttService;
import dagger.Provides;
import dagger.android.AndroidInjection;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this); (1)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
1 | Inject itself, all attributes with @Inject will be resolved. |
Update AndroidManifest
Finally if everything is done, lets update the manifest file:
...
...