The Future of Dependency Injection with Dagger 2 Notes

(This notes is from watching The Future of Dependency Injection with Dagger 2 by Jake Wharton)

Dependency Injection with Dagger 2

Dependency Injection

  • No “new”, dependencies come to you
  • First and foremost a pattern
  • Every single app has some form of DI

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Tweeter {
public void tweet(String tweet) {
TwitterApi api = new TwitterApi();
api.postTweet("xxx", tweet);
}
}
public class TweeterApi {
public void postTweet(String user, String tweet) {
OkHttpClient client = new OkHttpClient();
Request request = buildRequest();
client.newCall(request).execute();
}
}
Tweeter tweeter = new Tweeter();
tweeter.tweet("XYZ123")

Not very good though…

Pass dependencies to constructors

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Tweeter {
private final TwitterApi api;
private final String user;
public Tweeter(TwitterApi api, String user) {
this.api = api;
this.user = user;
}
public void tweet(String tweet) {
api.postTweet(user, tweet);
}
}

But this will make our calling code super verbose:

1
2
3
4
5
6
OkHttpClient client = new OkHttpClient();
TwitterApi api = new TwitterApi(client);
String user = "xxx";
Tweeter tweeter = new Tweeter(api, user);
tweeter.tweet("XYZ123");

So how to avoid these boilerplates?

Library Rescue

  • Spring
  • Guice
  • Dagger (v1)
  • PicoContainer
  • CDI

Guice

  • Powerful, dynamic, well-tested, wide-spread, etc…
  • Configuration problems occur at runtime
  • Slow initialization, slow injection, memory concerns

Dagger (v1)

  • Developed at Square by Jesse Wilson advised by Bob Lee
  • Initially targeted at highly resource constrained environments (e.g. Android)
  • Static analysis of all deps and injection points
  • Fail as early as possible (compile-time, not runtime)
  • Eliminate reflection on methods, fields, and annotations

  • Wide-spread, successful use in large Android applications

  • Triggers over-eager class loading on graph creation
  • FQCN (Fully Qualified Class Name) string-based keys in map-like data structure
  • Lack of full static scope analysis and scope annotations
  • Still uses reflection to lead generated classes

Dagger (v2)

  • Proposed and implemented by Java Core Libraries team
  • Eliminate runtime library and generated code overhead
  • Shift remaining runtime analysis to compile time
  • Scoping with annotations and associated static analysis

Dagger API

  • @Module + @Provides: mechanism for providing deps
  • @Inject: mechanism for requesting deps
  • @Component: bridge between modules and injections
  • Plus some othe suagr, magic, and conventions

Providing deps

  • Modules are classes whose methods provide deps
  • @Module on the class
  • @Provides (and friends) on each method
  • Designed to be partitioned and composed together
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Module
public class NetworkModule {
@Provides
@Singleton
OkHttpClient provideOkHttpClient() {
return new OkHttpClient();
}
@Provides
@Singleton
TwitterApi provideTwitterApi(OkHttpClient client) {
return new TwitterApi(client);
}
}

Dagger will create an internal object graph for these dependencies.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Module
public class TwitterModule {
private final String user;
public TwitterModule(String user) {
this.user = user;
}
@Provides
@Singleton
Tweeter provideTweeter(TwitterApi api) {
return new Tweeter(api, user);
}
@Provides
@Singleton
Timeline provideTimeline(TwitterApi api) {
return new Timeline(api, user);
}
}

Deps graph:

Deps Graph

Requesting deps

  • @Inject annotation required
  • Contructor, field, and method injection
Constructor Injection
  • @Inject on a single constructor
  • Constructor parameters are deps
  • Deps can be stored in private and final fields
  • Implicitly made avialable for downstream injection (so you do not need to write many providing methods!)
1
2
3
4
5
6
7
8
9
10
11
12
public class TwitterApplication {
private final Tweeter tweeter;
private final Timeline timeline;
@Inject
public TwitterApplication(Tweeter tweeter, Timeline timeline) {
this.tweeter = tweeter;
this.timeline = timeline;
}
// ...
}
Method Injection
  • @Inject on methods
  • Method parameters are deps
  • Injection happens after object is fully instantiated
  • Only one valid use case: passing this to a dep

e.g.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TwitterApplication {
private final Tweeter tweeter;
private final Timeline timeline;
@Inject
public TwitterApplication(Tweeter tweeter, Timeline timeline) {
this.tweeter = tweeter;
this.timeline = timeline;
}
@Inject
public void enableStreaming(Streaming streaming) {
streaming.register(this);
}
}
Field Injection
  • @Inject on field
  • Field may not be private or final
  • Injection happens after the object is fully instantiated
  • Object is usually responsible for or aware of injection
1
2
3
4
5
6
public class TwitterApplication {
@Inject Tweeter tweeter;
@Inject Timeline timeline;
//...
}

Field injection is very useful for Android.

Components

  • Bridge between modules and injection
  • The injector
1
2
3
4
5
6
7
8
9
@Singleton
@Component(modules = {
NetworkModule.class,
TwitterModule.class,
})
public interface TwitterComponent {
Tweeter tweeter();
Timeline timeline();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
TwitterComponent component = Dagger_TwitterComponent.builder()
// .networkModule(new NetworkModule()) unnecessary since dagger could automatically resolve parameterless modules
.twitterModule(new TwitterModule("xyz"))
.build();
Tweeter tweeter = component.tweeter();
tweeter.tweet("XYZ123");
Timeline timeline = component.timeline();
timeline.loadMore(20);
for (Tweet tweet : timeline.get()) {
System.out.println(tweet);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Singleton
@Component(modules = {
NetworkModule.class,
TwitterModule.class,
})
public interface TwitterComponent {
TwitterApplication app();
}
TwitterComponent component = Dagger_TwitterComponent.builder()
.twitterModule(new TwitterModule("XYZ"))
.build();
component.app().run();

What about field injection?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class TwitterApplication {
@Inject Tweeter tweeter;
@Inject Timeline timeline;
// ...
}
@Singleton
@Component(modules = {
NetworkModule.class,
TwitterModule.class,
})
public interface TwitterComponent {
void injectApp(TwitterApplication app);
}
TwitterApplication app = new TwitterApplication();
TwitterComponent component = Dagger_TwitterComponent.builder()
.twitterModule(new TwitterModule("XYZ"))
.build()
.injectApp(app);
app.run();
Implementation of scopes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Singleton
@Component(modules = NetworkModule.class)
public interface ApiComponent {
TwitterApi api(); // Needed since component need to expose the deps explictly
}
// @Singleton Can not be used here since @Singleton can not be used in a unscoped component
@Component(
dependencies = ApiComponent.class,
modules = TwitterModule.class
)
public interface TwitterComponent {
TwitterApplication app();
}
1
2
3
4
5
6
7
8
9
// When all dependent component are parameterless, dagger provide a convinent method create
ApiComponent apiComponent = Dagger_ApiComponent.create();
TwitterComponent twitterComponent = Dagger_TwitterComponent.builder()
.apiComponent(apiComponent)
.twitterModule(new TwitterModule("XYZ"))
.build();
twitterComponent.app().run();
Scope Annotations
  • Only creates a single instance
  • @Singleton is the “largest” scope (or topmost)
  • Custom annotations for semantic clarity, shorter lifetime
1
2
@Scope
public @interface User {}

To use it

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Module
public class TwitterModule {
private final String user;
public TwitterModule(String user) {
this.user = user;
}
@Provides
@User
Tweeter provideTweeter(TwitterApi api) {
return new Tweeter(api, user);
}
@Provides
@User
Timeline provideTimeline(TwitterApi api) {
return new Timeline(api, user);
}
}
@User
@Component(
dependencies = ApiComponent.class,
modules = TwitterModule.class
)
public interface TwitterComponent {
TwitterApplication app();
}

Deps Internal

Under The Hood

Dagger does this by annotation processor, then generate code to fulfill the dependency contract.

Annotation Processing

Another talk in http://bit.ly/apt-bd

JSR 330

Dagger 1 API

Dagger 2 API

More Concepts

  • Set and Map bindings
  • Asynchronous producers
  • Testing and module overrides
  • AutoFactory for assisted injection