Dependency Injection in Android with Dagger 2 and Kotlin
Dependency Injection in Android with Dagger 2 and Kotlin êŽë š
Update
This Dagger 2 tutorial is now up to date with the latest version of Android Studio, version 3.0.1, and uses Kotlin for app development. Update by Dario Coletto. Original tutorial by Joe Howard.
It turns out that Dependency Injection is nowhere near as complex as its name implies. And it is a key tool for building software systems that are maintainable and testable. In the end, relying on dependency injection will simplify your code quite a bit and also allow for a clearer path to writing testable code.
In this tutorial, youâll update an existing app named DroidWiki to use DI. The DroidWiki app is a simple wikipedia client for android, based on Dagger 2 and OkHttp 3. The Wikipedia API is based on MediaWiki, and you can find the documentation here. These API are public and there is no need to get API keys or other stuff. But you should definitely check out MediaWiki Etiquette.
Hereâs a quick peek at the search result screen in DroidWiki:
Introduction
Before we begin, if you donât know what Dependency Injection is, hereâs some great news: youâre probably already using it without knowing it!
What is Dependency Injection?
First, what is a dependency? Any non-trivial software program is going to contain components that pass information and send message calls back and forth between one another.
For example, when using an Object Oriented Programming language (such as Java/Kotlin on Android), objects will call methods on other objects that they have references to. A dependency is when one of the objects depends on the concrete implementation of another object.
Practical example of Dependency Injection
Consider a practical example in Kotlin code. You can identify a dependency in your code whenever you instantiate an object within another. In such a case, you are responsible for creating and configuring the object that is being created. For example, consider the following class Parent
:
class Parent {
private val child = Child()
}
A Parent
instance creates its child
field when itâs instantiated. The Parent
instance is dependent on the concrete implementation of Child
and on configuring the child
field to use it.
This presents a coupling or dependency of the Parent
class on the Child
class. If the setup of a Child
object is complex, all that complexity will be reflected within the Parent
class as well. You will need to edit Parent
to configure a Child
object.
If the Child
class itself depends on a class C
, which in turn depends on class D
, then all that complexity will propagate throughout the code base and hence result in a tight coupling between the components of the application.
Dependency Injection is the term used to describe the technique of loosening the coupling just described. In the simple example above, only one tiny change is needed:
class Parent(private val child: Child)
VoilĂ â thatâs dependency injection at its core!
Rather than creating the child object inside the Parent
class, the child
object is passed into or injected into Parent
âs constructor. The responsibility for configuring child
is elsewhere, and the Parent
class is a consumer of the Child
class.
The Dependency Inversion Principle
Dependency injection is often discussed in conjunction with one of the five SOLID principles of Object-Oriented Design: the Dependency Inversion principle. For a great introduction to the SOLID principles, particularly on Android, check out this post from Realm on Dependency Inversion.
The gist of the Dependency Inversion principle is that it is important to depend on abstractions rather than concrete implementations.
In the simple example above, this means changing Child
to a Kotlin interface
rather than a Kotlin class
. With this change, many different types of concrete Child
type objects that adhere to the Child
interface can be passed into the Parent
constructor. This presents several key benefits:
- Allows for the
Parent
class to be tested with various kinds ofChild
objects. - Mock
Child
objects can be used as needed in certain test scenarios. - Testing of
Parent
is independent of the implementation ofChild
.
How can Dagger 2 help with DI
Dagger 2 is the result of a collaboration between the team behind google/guice
(developed by Google) and Dagger (the predecessor of Dagger 2, created by Square).
They fixed a lot of problems from their previous work, and Dagger 2 is the faster framework for DI (since it works at compile time rather than at runtime with reflection).
Note
Dagger 2 is written in Java, but it works with Kotlin without any modification. However, the code generated by the annotation processor will be Java code (that is 100% interoperable with Kotlin).
The name âDaggerâ is inspired in part by the nature of dependencies in software development. The web of dependencies that occur between objects such as Parent
, Child
, OtherClass
, etc., create a structure called a Directed Acyclic Graph. Dagger 2 is used to simplify the creation of such graphs in your Java and Android projects.
Enough theory! Time to start writing some code.
Getting Started
Download the starter project [here]
.
Open the starter app in Android Studio 3.0.1 or greater and if it prompts you to update your gradle version, go ahead and do so.
The app consists of three screens. The first one is just a splashscreen.
In the second one youâll see an HTML version of the Wikipedia homepage, obtained through the WikiMedia API. The Toolbar
at the top of the page contains a magnifier that, when tapped, will lead you to the third screen.
From there, you can type something to search on Wikipedia and press the search button to run a query. Results will be displayed as a list of CardViews
inside a RecyclerView
.
Examine the app project structure and existing classes. Youâll see that the app uses the Model-View-Presenter (MVP) pattern to structure the code. Besides Dagger 2, the app uses common Android libraries such as:
- OkHttp 3
RecyclerView
- Kotlin synthetic properties
The subpackages in the main package are application
, model
, network
, ui
and utils
. If you explore the ui
package, youâll see subpackages for the three screens.
By examining the app build.gradle
file, youâll also see that the app applies the plugin kotlin-android-extensions
. It is used to implement view binding and kotlin-kapt
for annotation processing.
MVP
In case youâre unfamiliar with the MVP pattern, there are many good online resources for getting started.
MVP is similar to other structural patterns for implementing separation of concerns. Examples are Model-View-Controller and Model-View-ViewModel. In MVP on Android, your activities and fragments typically act as the view objects. They do so by implementing a view interface and handling interaction of the app with the user.
The view passes on user actions to the presenter, which handles the business logic and interaction with data repositories, such as a server API or database. The model layer consists of the objects that make up the content of the app.
In the case of DroidWiki, the HomepageActivity
class implements the HomepageView
interface. HomepageActivity
has a reference to a HomepagePresenter
interface. This controls access to model objects of type Homepage
The use of OkHttp 3 is found in the WikiApi
class in the network
package. This class defines the interface to the WikiMedia API required by the app. There are two calls of type GET defined and the JSONObject
will let you parse the obtained responses. The parsing will be executed in the HomepageResult
and the SearchResult
classes. And you will get a WikiHomepage
object and a list of Entry
objects.
Note
Since Kotlin doesnât need you to write getters and setters, you can replace POJO classes with a data class
. For more information, see the official kotlin data classes documentation.
Dependencies in DroidWiki
Open the HomepageActivity
class in the ui.homepage
package. In its onCreate()
method, there are several calls to configure a HomepagePresenter
:
private val presenter: HomepagePresenter = HomepagePresenterImpl()
...
presenter.setView(this)
presenter.loadHomepage()
Here, you create a concrete implementation of a HomepagePresenter
when the activity is instantiated.
Open HomepagePresenterImpl.kt
and take a look at loadHomepage()
. Both an OkHttpClient
object and a WikiApi
object are created and configured in the loadHomepage()
method. The same happens for the SearchActivity
with an EntryPresenter
and a list of EntryView
.
This creation of dependencies couple the model, view, and presenter layers too tightly. Swapping in a mock presenter for the view is impossible as written without updating the view code. The code for creating the OkHttpClient
and WikiApi
objects is repeated between the two presenter implementations in the app: HomepagePresenterImpl
and EntryPresenterImpl
.
Finally, itâs time to start writing some code to remove code duplication and some coupling between layers!
Configure the Project With Dagger 2
Configuring Dagger with Kotlin is a little bit different from how you may have done with Java. Dagger requires an annotation processor, and thus the main difference is which one you are going to use. In fact with Java you used the Groovy methods apt
or the newer annotationProcessor
, while with Kotlin you need to use kapt
.
By default kapt is not enabled in Kotlin, and to enable it you must apply its plugin to your app build.gradle
, so add the line apply plugin: 'kotlin-kapt'
:
plugins {
id("com.android.application")
id("kotlin-android")
id("kotlin-kapt")
}
Then add these dependencies to actually âinstallâ dagger
dependencies {
// ...
implementation 'com.google.dagger:dagger:2.11'
kapt 'com.google.dagger:dagger-compiler:2.11'
provided 'javax.annotation:jsr250-api:1.0'
// ...
}
Here weâre using Dagger 2.11, you can check the latest version in the Maven repository.
Android Studio will prompt you to sync your gradle files on this change, so please go ahead and do so to ensure that youâre including Dagger correctly. Notice that you are including an annotation library from javax
, because many of the features of Dagger are provided by Java annotations.
Dagger 2 public APIs
Dagger 2 can seem complex at the beginning, but itâs really not so hard. In fact, the complete public API of Dagger is composed by less than 30 lines of code. And from these lines you can remove a pair of interfaces that are rarely used (Lazy and MapKey), so the most used public APIs are composed of 3 annotations:
public @interface Component {
Class<?> [] modules() default {};
Class<?> [] dependencies() default {};
}
public @interface Module {
Class<?> [] includes() default {};
}
public @interface Provides {}
Now letâs see how these annotations are used to bring dependency injection to your projects!
Module
The first annotation youâll use is the @Module
annotation.
Next, create a new file in the dagger
package.
Add the following empty class to the new file:
@Module
class AppModule {
}
Here, youâve created a class named AppModule
and annotated it with the Dagger @Module
annotation. Android Studio should automatically create any necessary import statements for you. But if not, hit option+return on Mac or Alt+Enter on PC to create them as needed.
The @Module
annotation tells Dagger that the AppModule
class will provide dependencies for a part of the application. It is normal to have multiple Dagger modules in a project, and it is typical for one of them to provide app-wide dependencies.
Add that capability now by inserting the following code within the body of the AppModule
class:
@Module
class AppModule(private val app: Application) {
@Provides
@Singleton
fun provideContext(): Context = app
}
Youâve added a private field to hold a reference to the app
object, a constructor to configure app
, and a provideContext()
method that returns the app
object. Notice that there are two more Dagger annotations on that method: @Provides
and @Singleton
.
@Provides
and @Singleton
The @Provides
annotation tells Dagger that the method provides a certain type of dependency, in this case, a Context
object. When a part of the app requests that Dagger inject a Context
, the @Provides
annotation tells Dagger where to find it.
Note
The method names for the providers, such as provideContext()
, are not important and can be named anything you like. Dagger only looks at the return type. Using provide
as a prefix is a common convention.
The @Singleton
annotation is not part of the Dagger API. Itâs contained inside the javax package you added to your build.gradle at the beginning. It tells Dagger that there should only be a single instance of that dependency. So when generating the code Dagger will handle all the logic for you, and you wonât write all the boilerplate code to check if another instance of the object is already available.
Component
Your first (dependency) injection
Update HomepageActivity
The General Pattern
Injecting the Network Graph
NetworkModule
Simplifying API builder
WikiModule
Update PresenterModule
Update WikiApi
Where to Go From Here?
You can download the final project here.
A lot of developers asks themselves if all the changes youâve applied to the DroidWiki app are useful or not. Especially since everything was already working before implementing dependency injection. The utility of dependency injection and a framework like Dagger 2 become most evident in real-world production apps, where the dependency graph can get very complex.
Dagger 2 and dependency injection become especially useful when implementing proper testing into your app, allowing mock implementations of back-end APIs and data repositories to be used in testing.
Thereâs much more to learn about in Dagger 2 and its usage, including:
- Scopes
- Subcomponents
- Testing with Mockito
There are many great resources out there on the interwebs to dive into these topics and one I must suggest is a talk by Jake Wharton at DevOxx, where you can get some more information about the history of DI on Android, some theory and some nice examples. As you do, happy injecting!
If you have any questions or comments, please join the discussion below!