That Missing Guide: How to use Dagger2
Dagger is a dependency injection framework, which makes it easier to manage the dependencies between the classes in our app. If you’ve never heard of
dependency injection
before, then check out this quick ELI5 from /u/ronshapiro, member of the Dagger2 team.
Back in the day, about 2.5 years ago when Dagger2 came out, I was excited that Google has created a fork of the original Dagger (created by Square).
The original Dagger was partly reflection-based, and didn’t play well with Proguard. Dagger2 however is based entirely on annotation processing, so it does the magic at compile time. It works without any additional Proguard configuration, and is generally faster.
Buuuuut, while the docs have been getting better, they’re still mostly about brewing coffee with thermosiphons and heaters. I’ve never had either of those in a real app.
So with Koin and Kodein and other random emergent libraries trying to claim they’re the “simplest DI solutions” that make “dependency injection painless”, I figured it’s time to write a (or yet another?) quick guide that shows you just how simple it actually is to use Dagger2.
The steps:
- Ignore
dagger-android
(added in 2.10) entirely, you don’t need it - Use
@Inject
annotated constructors wherever you can (and the scope that comes with it on the class) - Use
@Component
, and@Module
(when you actually need it) - Understand
@Singleton
scope (basically “allow creating only 1 instance of this thing, please”), and that all scopes say the same thing - Start using subscopes only if you actually have an architectural design that demands the creation of subscopes. You can usually get away without ’em. Either way, don’t stress about ‘em.
How to add Dagger to your project
Into your build.gradle dependencies
, as usual
annotationProcessor ‘com.google.dagger:dagger-compiler:2.16.1’
implementation ‘com.google.dagger:dagger:2.16.1’
compileOnly ‘org.glassfish:javax.annotation:10.0-b28’
Of course, with Kotlin, replace annotationProcessor
with kapt
(and don’t forget the apply plugin: ‘kotlin-kapt'
either).
How to tell if you’re doing it wrong
I actually made this mistake a long time ago, and a lot of guides still make the same mistake as well. In fact, I only figured out that this is wrong because of a japanese article that mentions it, so there’s that.
Let’s assume you have this:
public class MyServiceImpl implements MyService {
private final ApiService apiService; public MyServiceImpl(ApiService apiService) {
this.apiService = apiService;
}
}
And here’s what you DON’T do:
@Module
public class ServiceModule {
@Provides
@Singleton
public MyService myService(ApiService apiService) {
return new MyServiceImpl(apiService); // <-- NO
}
}
And instead this is what you DO, assuming you really do need to bind an implementation to an interface:
@Module
public class ServiceModule {
@Provides
MyService myService(MyServiceImpl service) {
return service; // <-- YES
}
}@Singleton // <-- YES
public class MyServiceImpl implements MyService {
private final ApiService apiService; @Inject // <-- YES
public MyServiceImpl(ApiService apiService) { // <-- YES
this.apiService = apiService;
}
}
Okay, so there’s one more thing that can easily simplify this whole thing. And no, I don’t mean using @Binds
(which does the exact same thing, except it’s an abstract
method)
If I don’t need an interface for a class, because I don’t have multiple subclasses of it at runtime execution (and can otherwise just mock the thing for tests), then I don’t need the interface. And then I no longer need the module.
@Singleton
public class MyService {
private final ApiService apiService; @Inject
public MyService(ApiService apiService) {
this.apiService = apiService;
}
}
Boom, that’s my Dagger configuration for MyService
. Adding @Singleton
and adding @Inject
and it works.
I’d say that’s better than Kodein’s instance()
chain, and by provider with
, right?
A word of caution about interfaces
As a response to some valid criticism, I must make a note though — I’m not saying that you should remove every single interface you’ve ever written, but you should consider whether you actually do need that interface.
If it helps in your tests, for example configuring a different data source, then by all means use an interface! Kotlin makes the binding method a single-liner anyways.
@Provides fun myService(impl: MyServiceImpl) : MyService = impl;
Let’s walk through a real example
I want to download a batch of cats, and show it on the screen in a RecyclerView. As I scroll down, I want to download more cats, and save them in a database across at least rotations.
The problem: A world with manual DI, without a DI framework
With enough punching, we end up with the following classes:
Context appContext;
CatTable catTable;
CatDao catDao;
CatMapper catMapper;
CatRepository catRepository;
CatService catService;
Retrofit retrofit;
DatabaseManager databaseManager;
Scheduler mainThreadScheduler;
Scheduler backgroundThreadScheduler;
And in our application, we could set these classes up ourselves manually like this:
this.appContext = this;
this.mainThreadScheduler = new MainThreadScheduler();
this.backgroundThreadScheduler = new BackgroundScheduler();
this.catTable = Tables.CAT;
this.databaseManager = new DatabaseManager(
appContext, Tables.getTables());
this.catMapper = new CatMapper();
this.retrofit = new Retrofit.Builder()
.baseUrl("http://thecatapi.com/")
.addConverterFactory(SimpleXmlConverterFactory.create())
.build();
this.catService = retrofit.create(CatService.class);
this.catDao = new CatDao(catTable, catMapper, databaseManager);
this.catRepository = new CatRepository(catDao, catService,
catMapper, backgroundThreadScheduler, mainThreadScheduler);
These are just simple classes to get a batch of cats… and yet, that’s a lot of manual configuration.
And the order of these instantiations matters! That repository will throw an NPE if you accidentally try to create it before creating the dao, or the service, or the mapper. But only at runtime!
Using constructor injection + scope
We can actually make this much easier if we just use Dagger2 with constructor injection. And to keep it short, let’s assume Kotlin.
@Singleton
class CatMapper @Inject constructor() {
}@Singleton
class CatDao @Inject constructor(
private val catTable: CatTable,
private val catMapper: CatMapper,
private val databaseManager: DatabaseManager) {
}@Singleton
class CatRepository @Inject constructor(
private val catDao: CatDao,
private val catService: CatService,
private val catMapper: CatMapper,
private @Named("BACKGROUND") val backgroundThread: Scheduler,
private @Named("MAIN") val mainThread: Scheduler) {
}
We can freely use all the things we’ve received in the constructor, and they’re all available, and provided from the outside. Dagger will figure out the “how”.
Using @Module
for things that Dagger can‘t figure out
Providing things without @Inject constructor
It’s great that we can use constructor injection for things we own, but what about things built with builders, or from other places?
That’s what modules are for.
In this case, Retrofit
, and the implementation created by Retrofit.
@Module
class ServiceModule {
@Provides
@Singleton
fun retrofit(): Retrofit = Retrofit.Builder()
.baseUrl("http://thecatapi.com/")
.addConverterFactory(SimpleXmlConverterFactory.create())
.build(); @Provides
@Singleton
fun catService(retrofit: Retrofit): CatService =
retrofit.create(CatService::class.java);
}
Okay, so now Dagger knows how to provide these things, we can use them directly in constructor injection. Neat.
Providing existing instances
Let’s say we want the application context too. We want to provide that and we don’t own it, right? So we need a module for it.
@Module
class ContextModule(private val appContext: Context) {
@Provides
fun appContext(): Context = appContext;
}
And
val contextModule = ContextModule(this); // this === app
Boom, done.
You could be using @BindsInstance
and manually define your own @Component.Builder
to do literally the exact same thing (providing the externally provided Context) with triple the configuration, but as it does the same thing and it’s more complicated, we don’t really need to care. Just use module with constructor argument.
Providing named implementations
In our example, we cannot avoid using BackgroundScheduler
and MainThreadScheduler
as instances of Scheduler
(to allow passing in ImmediateScheduler
), so we’ll have to add a module for that as well.
@Module
class SchedulerModule {
@Provides
@Singleton
@Named("BACKGROUND")
fun backgroundScheduler(): Scheduler = BackgroundScheduler(); @Provide
@Singleton
@Named("MAIN")
fun mainThreadScheduler(): Scheduler = MainThreadScheduler();
}
@Component: the thing that actually makes us things
We’ve described with constructor injection and modules how things can be created. Now we need the thing that can make things. A collection of providers, factories, you name it. It’s called a @Component
, but you could also call it ObjectGraph
, and it’d be the same thing.
I will definitely NOT call it NetComponent
(because that doesn’t make any sense), I’ll call it SingletonComponent
.
It looks like this:
@Component(modules={ServiceModule.class,
ContextModule.class,
SchedulerModule.class})
@Singleton
public interface SingletonComponent {
// nothing here initially
}
Super-complex, right? The problem with it is that unless we put in either methods that allow field injection for concrete types, or provision methods, we can’t really get anything from the component.
So you can either add
void inject(MyActivity myActivity);
or
Context appContext();
CatDao catDao();
...
Personally I think that field injection scales rather badly, so I prefer to put in a provision method for every dependency I need, and then pass my component around. So it looks like this.
@Component(modules={ServiceModule.class,
ContextModule.class,
SchedulerModule.class})
@Singleton
public interface SingletonComponent {
Context appContext();
CatTable catTable();
CatDao catDao();
CatMapper catMapper();
CatRepository catRepository();
CatService catService();
Retrofit retrofit();
DatabaseManager databaseManager();
@Named("MAIN") Scheduler mainThreadScheduler();
@Named("BACKGROUND") Scheduler backgroundThreadScheduler();
}
That might look like a bunch of methods, but this is the reason why Dagger is able to figure out any of our dependency when we want it, and still ensure there is only a single instance of them (if they’re scoped) no matter what.
It’s actually quite convenient.
Creating an instance of the component
This is a singleton component, and to create a process-level singleton, we can use Application.onCreate()
as our choice.
class CustomApplication: Application() {
lateinit var component: SingletonComponent; override fun onCreate() {
super.onCreate();
component = DaggerSingletonComponent.builder()
.contextModule(ContextModule(this))
.build();
}
}
The DaggerSingletonComponent
was created by Dagger itself, and it’s a bunch of code we’d need to write for proper DI. The framework “writes” it for us instead.
Now if we have a reference to our Application class, we can retrieve any class, fully injected — or use it to inject our activities/fragments (which are created by the system using their no-args constructor).
Accessing the component, and injecting Activities/Fragments
A trick I like to do is make the injector accessible from anywhere, even without the application instance.
class CustomApplication: Application() {
lateinit var component: SingletonComponent
private set override fun onCreate() {
super.onCreate()
INSTANCE = this
component = DaggerSingletonComponent.builder()
.contextModule(ContextModule(this))
.build()
} companion object {
private var INSTANCE: CustomApplication? = null @JvmStatic
fun get(): CustomApplication = INSTANCE!! }
}
Then I can do
class Injector private constructor() {
companion object {
fun get() : SingletonComponent =
CustomApplication.get().component;
}
}
So now in my Activity, I can do
class CatActivity: AppCompatActivity() {
private val catRepository: CatRepository; init {
this.catRepository = Injector.get().catRepository();
}
}
With that, we can actually get anything we want into our Activities. One could say “hey hold on, you’re relying on service-lookup”, but I think that’s the best/most stable thing you can do for an Activity/Fragment.
It is NOT the best thing for classes you actually own, so why would I use a service lookup for those? Just use @Inject
!
…But I want a presenter that is not a singleton
Then we have three options:
- Create an unscoped presenter, and let it die with the Activity/Fragment on configuration change
- Create an unscoped presenter and keep it for the scope of the Activity/Fragment
- Create a scoped subcomponent and a subscoped presenter
And they’re right!
So I won’t even need to talk in-depth about subscoping — while I like the concept, we can actually avoid using subcomponents/component dependencies. You can generally get away with a mix of singleton scoped and unscoped things, and you can get them from the same Injector
without having to juggle the subscoped component around for the duration of their scope.
I must admit that creating subscoped components with the right lifecycle is easy now, you’d just need to do it in ViewModelProviders.Factory
for ViewModel
.
Anyways, here’s an unscoped presenter:
// UNSCOPED
class CatPresenter @Inject constructor(
private val catRepository: CatRepository
) {
...
}
This way, our Activity can get this instance… and if we want to retain it across config change, that’s not so hard either:
class CatActivity: AppCompatActivity() {
private lateinit var catPresenter: CatPresenter; override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState);
val nonConfig = getLastCustomNonConfigurationInstance();
if(nonConfig != null) {
this.catPresenter = nonConfig as CatPresenter;
} else {
this.catPresenter = Injector.get().catPresenter();
}
} override fun onRetainCustomNonConfigurationInstance() =
catPresenter;
}
This way, the presenter itself is unscoped, but it is only created when actually needed. We could also use the ViewModel
from newly released Architecture Components. We did not need to create a subcomponent for it.
But what about subscopes?
If you’re still interested in using subscopes and @Subcomponent
and all that, feel free to check out this revamped Mortar sample with Dagger2.
Providing data as a subscoped dependency in the form of a Observable<T>
is just super cool. Jake Wharton’s been doing that for ages and we’re still just catching up.
In general, we could easily avoid using subscopes, by keeping the unscoped dependency alive, instead of subscoping the component then keeping the subscoped component alive.
But for subscoping, you can experiment with both @Subcomponent
and @Component(dependencies={...})
, they do the same thing: inherit providers from the parent.
But what about Dagger-Android?
While it allows you to auto-inject Activities/Fragments with a hierarchical lookup, it’s quite magical to set up. It’s not as configurable; personally I can’t seem to figure out how to create scoping, if I actually did want to do scoping.
So I don’t use it, and I personally don’t advise using it. There are tutorials out there for making@ContributesXInjector
work for the curious, though.
Conclusion
Hopefully that helped you see how much simpler it is to use Dagger2 than the nightmarish “learning curve” that people talk about.
It’s really just:
- Apply
@Singleton
and@Inject
- Create
@Module
where you can’t do@Inject
- Create
@Component
and use component
That’s not so hard, is it?