I come with knifes
A story of Daggers and Toothpicks…
Droidcon Krakow 2016
Danny Preussler
@PreusslerBerlin
@PreusslerBerlin
Once upon a time
@PreusslerBerlin
A single activity
@PreusslerBerlin
public class LonelyActivity extends Activity {
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_main);
}
}
@PreusslerBerlin
needs to
@PreusslerBerlin
needs to support
@PreusslerBerlin
needs to support
Tracking
@PreusslerBerlin
The journey to testability starts
@PreusslerBerlin
Starring
@PreusslerBerlin
Tracker as component
public interface Tracker {
void trackStarted();
}
@PreusslerBerlin
GoogleAnalyticsTracker as Tracker
public class GoogleAnalyticsTracker
implements Tracker {
@Override
public void trackStarted() {
}
}
@PreusslerBerlin
Prolog
The Untestable
@PreusslerBerlin
public class LonelyActivity extends Activity {
private final Tracker tracker
= new GoogleAnalyticsTracker();
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_main);
tracker.trackStarted();
}
}
@PreusslerBerlin
public class LonelyActivity extends Activity {
private final Tracker tracker
= new GoogleAnalyticsTracker();
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_main);
tracker.trackStarted();
}
}
@PreusslerBerlin
public class LonelyActivity extends Activity {
private final Tracker tracker
= new GoogleAnalyticsTracker();
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_main);
tracker.trackStarted();
}
}
Not testable!
@PreusslerBerlin
A voice out of the dark:
Inversion of Control!
@PreusslerBerlin
A voice out of the dark:
Inversion of Control!
@PreusslerBerlin
public class LonelyActivity extends Activity {
private final Tracker tracker
= new GoogleAnalyticsTracker();
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_main);
tracker.trackStarted();
}
}
@PreusslerBerlin
Act I
The Factory
@PreusslerBerlin
public class Dependencies {
static Dependencies instance = new Dependencies();
public static Dependencies getInstance() {
return instance;
}
public Tracker getTracker() {
return new GoogleAnalyticsTracker();
}
…
@PreusslerBerlin
public class Dependencies {
static Dependencies instance = new Dependencies();
public static Dependencies getInstance() {
return instance;
}
public Tracker getTracker() {
return new GoogleAnalyticsTracker();
}
…
@PreusslerBerlin
public class Dependencies {
static Dependencies instance = new Dependencies();
public static Dependencies getInstance() {
return instance;
}
public Tracker getTracker() {
return new GoogleAnalyticsTracker();
}
…
@PreusslerBerlin
import static Dependencies.getInstance;
class LonelyActivity extends Activity {
Tracker tracker = getInstance().getTracker();
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
...
tracker.trackStarted();
}
}
@PreusslerBerlin
import static Dependencies.getInstance;
class LonelyActivity extends Activity {
Tracker tracker = getInstance().getTracker();
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
...
tracker.trackStarted();
}
}
@PreusslerBerlin
@Mock Tracker tracker;
@Before
public void setup() {
…
Dependencies.instance = mock(Dependencies.class);
when(Dependencies.instance.getTracker())
.thenReturn(tracker);
}
@Test
public void should_track() {
new SimpleActivity().onCreate(null);
verify(tracker).trackStarted();
}
@PreusslerBerlin
@Mock Tracker tracker;
@Before
public void setup() {
…
Dependencies.instance = mock(Dependencies.class);
when(Dependencies.instance.getTracker())
.thenReturn(tracker);
}
@Test
public void should_track() {
new SimpleActivity().onCreate(null);
verify(tracker).trackStarted();
}
@PreusslerBerlin
@Mock Tracker tracker;
@Before
public void setup() {
…
Dependencies.instance = mock(Dependencies.class);
when(Dependencies.instance.getTracker())
.thenReturn(tracker);
}
@Test
public void should_track() {
new SimpleActivity().onCreate(null);
verify(tracker).trackStarted();
}
@PreusslerBerlin
+/- Somehow testable
- Not very dynamic
- Lots of code
@PreusslerBerlin
Act II
The Map
@PreusslerBerlin
public class Dependencies {
static Map<Class, Class> modules = new HashMap<>();
static {
modules.put(
Tracker.class,
new GoogleAnalyticsTracker());
}
public static <T> T get(Class<T> clzz) {
return (T) modules.get(clzz);
}
}
@PreusslerBerlin
public class Dependencies {
static Map<Class, Class> modules = new HashMap<>();
static {
modules.put(
Tracker.class,
new GoogleAnalyticsTracker());
}
public static <T> T get(Class<T> clzz) {
return (T) modules.get(clzz);
}
}
@PreusslerBerlin
import static Dependencies.getInstance;
class LonelyActivity extends Activity {
Tracker tracker = getInstance().get(Tracker.class);
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
...
tracker.trackStarted();
}
}
@PreusslerBerlin
import static Dependencies.getInstance;
class LonelyActivity extends Activity {
Tracker tracker = getInstance().get(Tracker.class);
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
...
tracker.trackStarted();
}
}
@PreusslerBerlin
+ Testable
+ Dynamic
- Instances created at startup
@PreusslerBerlin
Act III
Reflection(s)
@PreusslerBerlin
public class Dependencies {
private final static Map<Class, Class> modules
= new HashMap<>();
static {
modules.put(
Tracker.class,
GoogleAnalyticsTracker.class);
}
static <T> T get(Class<T> clzz) {
…
@PreusslerBerlin
public class GoogleAnalyticsTracker implements Tracker {
GoogleAnalyticsTracker(Activity activity) {
...}
GoogleAnalyticsTracker(Application application) {
...}
GoogleAnalyticsTracker(Context context) {
...}
...
@PreusslerBerlin
public class GoogleAnalyticsTracker implements Tracker {
GoogleAnalyticsTracker(Activity activity) {
...}
GoogleAnalyticsTracker(Application application) {
...}
@Inject
GoogleAnalyticsTracker(Context context) {
...}
...
@PreusslerBerlin
package javax.inject;
@Target({ METHOD, CONSTRUCTOR, FIELD })
@Retention(RUNTIME)
public @interface Inject {}
@PreusslerBerlin
public class Dependencies {
private final static Map<Class, Class> modules
= new HashMap<>();
static {
modules.put(
Tracker.class,
GoogleAnalyticsTracker.class);
}
static <T> T get(Class<T> clzz) {
…
@PreusslerBerlin
static <T> T get(Class<T> clzz) {
…
Constructor<?>[] constructors =
clzz.getDeclaredConstructors();
for(Constructor ctr: constructors) {
@PreusslerBerlin
static <T> T get(Class<T> clzz) {
…
Constructor<?>[] constructors =
clzz.getDeclaredConstructors();
for(Constructor ctr: constructors) {
@PreusslerBerlin
...
if(ctr.getDeclaredAnnotation(Inject.class)
!= null) {
Class[] types = ctr.getParameterTypes();
Object[] params =
new Object[types.length];
for (int i=1; i< params.length; i++) {
params[i] = get(types[i]);
}
@PreusslerBerlin
if(ctr.getDeclaredAnnotation(Inject.class)
!= null) {
Class[] types = ctr.getParameterTypes();
Object[] params =
new Object[types.length];
for (int i=1; i< params.length; i++) {
params[i] = get(types[i]);
}
@PreusslerBerlin
if(ctr.getDeclaredAnnotation(Inject.class)
!= null) {
Class[] types = ctr.getParameterTypes();
Object[] params =
new Object[types.length];
for (int i=1; i< params.length; i++) {
params[i] = get(types[i]);
}
@PreusslerBerlin
...
try {
return (T) ctr.newInstance(params);
} catch (Exception e) {
...
}
@PreusslerBerlin
import static Dependencies.getInstance;
class LonelyActivity extends Activity {
Tracker tracker = getInstance().get(Tracker.class);
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
...
tracker.trackStarted();
}
}
@PreusslerBerlin
import static Dependencies.getInstance;
class LonelyActivity extends Activity {
Tracker tracker = getInstance().get(Tracker.class);
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
...
tracker.trackStarted();
}
}
@PreusslerBerlin
+ Testable
+ Dynamic
+ Flexible
@PreusslerBerlin
Can we improve that?
@PreusslerBerlin
import static Dependencies.getInstance;
class LonelyActivity extends Activity {
Tracker tracker = getInstance().get(Tracker.class);
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
...
tracker.trackStarted();
}
}
@PreusslerBerlin
import static Dependencies.getInstance;
class LonelyActivity extends Activity {
Tracker tracker = getInstance().get(Tracker.class);
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
...
tracker.trackStarted();
}
}
@PreusslerBerlin
import static Dependencies.getInstance;
class LonelyActivity extends Activity {
@Inject Tracker tracker;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
...
tracker.trackStarted();
}
}
@PreusslerBerlin
import static Dependencies.getInstance;
class LonelyActivity extends Activity {
@Inject Tracker tracker;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
Dependencies.inject(this);
...
tracker.trackStarted();
}
}
@PreusslerBerlin
static void inject(Object instance) {
Field[] fields =
instance.getClass().getDeclaredFields();
for (Field f: fields) {
if (f.getDeclaredAnnotation(Inject.class) != null){
try {
field.set(instance, get(f.getType()));
} catch (IllegalAccessException e) {...}
...
@PreusslerBerlin
static void inject(Object instance) {
Field[] fields =
instance.getClass().getDeclaredFields();
for (Field f: fields) {
if (f.getDeclaredAnnotation(Inject.class) != null){
try {
field.set(instance, get(f.getType()));
} catch (IllegalAccessException e) {...}
...
@PreusslerBerlin
static void inject(Object instance) {
Field[] fields =
instance.getClass().getDeclaredFields();
for (Field f: fields) {
if (f.getDeclaredAnnotation(Inject.class) != null){
try {
field.set(instance, get(f.getType()));
} catch (IllegalAccessException e) {...}
...
@PreusslerBerlin
static void inject(Object instance) {
Field[] fields =
instance.getClass().getDeclaredFields();
for (Field f: fields) {
if (f.getDeclaredAnnotation(Inject.class) != null){
try {
field.set(instance, get(f.getType()));
} catch (IllegalAccessException e) {...}
...
@PreusslerBerlin
OMG,
We just built
@PreusslerBerlin
OMG,
We just built
Guice!
@PreusslerBerlin
OMG,
We just built
RoboGuice!
@PreusslerBerlin
OMG,
We just built
RoboGuice!
@PreusslerBerlin
import static Dependencies.getInstance;
class LonelyActivity extends Activity {
@Inject Tracker tracker;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
Dependencies.inject(this);
...
tracker.trackStarted();
}
}
@PreusslerBerlin
- Reflection is slow
@PreusslerBerlin
"Avoid dependency injection frameworks”
- Google
developer.android.com/training/articles/memory.html#DependencyInjection
till early 2016
@PreusslerBerlin
Act IV
Compiler Magic
@PreusslerBerlin
Annotation processing
&
Code generation
@PreusslerBerlin
Here comes
Dagger as Hero
@PreusslerBerlin
The end!?
@PreusslerBerlin
In its first screen appearance:
Toothpick
as unknown stranger
@PreusslerBerlin
“YOUR boilerplate sucks”
@PreusslerBerlin
“YOU don’t even know
how to unit test!”
@PreusslerBerlin
Fight!
@PreusslerBerlin
The module
@PreusslerBerlin
@Module
static class BaseModule {
...
BaseModule(Application application) {
...
}
@Provides
public Tracker provideTracker() {
return new GoogleAnalyticsTracker();
}
}
@PreusslerBerlin
class BaseModule extends Module {
public BaseModule(Application application) {
bind(Tracker.class)
.to(GoogleAnalyticsTracker.class);
...
@PreusslerBerlin
The component
@PreusslerBerlin
@Component(modules = {BaseModule.class})
interface AppComponent {
void inject(LonelyActivity activity);
}
@PreusslerBerlin
“WHAT component???”
@PreusslerBerlin
Setup
Dagger 0 : 1 Toothpick
@PreusslerBerlin
Usage
@PreusslerBerlin
class LonelyActivity extends Activity {
@Inject Tracker tracker;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
...
tracker.trackStarted();
}
}
@PreusslerBerlin
class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
DaggerDependencies_AppComponent
.builder()
.baseModule(
new BaseModule(this))
.build()
@PreusslerBerlin
class LonelyActivity extends Activity {
@Inject Tracker tracker;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
getApplication().getComponent().inject(this);
tracker.trackStarted();
}
}
@PreusslerBerlin
Scope scope =
openScope("APPLICATION")
scope.installModules(
new BaseModule(application));
@PreusslerBerlin
import static Toothpick.openScope;
class LonelyActivity extends Activity {
@Inject Tracker tracker;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
openScope("APPLICATION").inject(this);
tracker.trackStarted();
}
}
@PreusslerBerlin
Usage
Dagger 1 : 2 Toothpick
@PreusslerBerlin
Testing
@PreusslerBerlin
Testing
@PreusslerBerlin
@Mock Tracker tracker;
class TestModule extends Dependencies.BaseModule {
TestModule() {
super(mock(Application.class));
}
@Provides
public Tracker provideTracker() {
return tracker;
}
}
}
@PreusslerBerlin
@Mock Tracker tracker;
class TestModule extends Dependencies.BaseModule {
TestModule() {
super(mock(Application.class));
}
@Provides
public Tracker provideTracker() {
return tracker;
}
}
}
@PreusslerBerlin
@Test
public void should_track() {
Application.set(
DaggerDependencies_AppComponent
.builder().baseModule(
new TestModule()).build());
new LonelyActivity().onCreate(null);
verify(tracker).trackStarted();
}
...
}
@PreusslerBerlin
@Test
public void should_track() {
Application.set(
DaggerDependencies_AppComponent
.builder().baseModule(
new TestModule()).build());
new LonelyActivity().onCreate(null);
verify(tracker).trackStarted();
}
...
}
@PreusslerBerlin
@Mock Tracker tracker;
@Rule
public ToothPickRule toothPickRule =
new ToothPickRule(this, "APPLICATION_SCOPE");
@Test
public void should_track() {
new LonelyActivity().onCreate(null);
verify(tracker).trackStarted();
}
@PreusslerBerlin
@Mock Tracker tracker;
@Rule
public ToothPickRule toothPickRule =
new ToothPickRule(this, "APPLICATION_SCOPE");
@Test
public void should_track() {
new LonelyActivity().onCreate(null);
verify(tracker).trackStarted();
}
@PreusslerBerlin
Testing
Dagger 1 : 3 Toothpick
@PreusslerBerlin
Scopes
@PreusslerBerlin
@Singleton
@PreusslerBerlin
@Scope
@Retention(RUNTIME)
public @interface ActivityScope {}
@ActivityScope
@Subcomponent(modules = {ScopeModule.class})
interface ScopeComponent {
void inject(ScopeActivity activity);
}
@PreusslerBerlin
@Module
static class ScopeModule {
private Activity activity;
public ScopeModule(Activity activity) {
this.activity = activity;
}
@Provides @ActivityScope
public Activity provideActivity() {
return activity;
}
}
@PreusslerBerlin
@Component(modules = {BaseModule.class})
interface AppComponent {
void inject(LonelyActivity activity);
ScopeComponent plus(ScopeModule module);
}
@PreusslerBerlin
“That’s Annotation Porn!”
@PreusslerBerlin
public class ScopeActivity extends Activity {
…
private ScopeComponent scope;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_main);
scope = createScope(this);
scope.inject(this);
…
}
// call from fragments...
public ScopeComponent getScope() {
return scope;
}
}
@PreusslerBerlin
public class ScopeActivity extends Activity {
…
private ScopeComponent scope;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_main);
scope = createScope(this);
scope.inject(this);
…
}
// call from fragments...
public ScopeComponent getScope() {
return scope;
}
}
@PreusslerBerlin
“Stop this madness!”
@PreusslerBerlin
class ScopeModule extends Module {
public ScopeModule(Activity activity) {
bind(Activity.class).toInstance(activity);
}
@PreusslerBerlin
Scope scope =
openScopes(”APPLICATION_SCOPE”,
activity.getClass().getName());
scope.installModules(
new ScopeModule(activity));
@PreusslerBerlin
Scope scope =
openScopes(”APPLICATION_SCOPE”,
activity.getClass().getName());
scope.installModules(
new ScopeModule(activity));
@PreusslerBerlin
closeScope(
activity.getClass().getName());
@PreusslerBerlin
Scopes
Dagger 1 : 4 Toothpick
@PreusslerBerlin
Performance
1000 injections
• Dagger 1: 33 ms
• Dagger 2: 31 ms
• Toothpick: 35 ms
6400 injections
• Dagger 1: 45 ms
• Dagger 2: 42 ms
• Toothpick: 66 ms
@PreusslerBerlin
Performance
Dagger 2 : 4 Toothpick
@PreusslerBerlin
And there is more:
SmoothieModule
@PreusslerBerlin
Ease of
use
Speed
Roboguice
Dagger
Toothpick
@PreusslerBerlin
Dagger:
+ compile safe
+ feature rich
+ performant
@PreusslerBerlin
Dagger:
-heavy weight
- top down
- annotation porn
- hard on unit tests
@PreusslerBerlin
Toothpick:
+ light weight
+ bottom up
+ less boilerplate
+ testing focus
@PreusslerBerlin
Toothpick:
-not idiot proof
- performance
@PreusslerBerlin
Choose the
right tool
for your
problem!
ABetterToolbyTheMarmot,CCby2.0,flickr.com/photos/themarmot/6283203163
@PreusslerBerlin
github.com/
dpreussler/
understand_dependencies
@PreusslerBerlin
TP written by
Stephane Nicolas
Daniel Molinero Reguera
Presented by
Danny Preussler
Special thanks to
Florina Muntenescu

Demystifying dependency Injection: Dagger and Toothpick

Editor's Notes

  • #3 A code base, one activity, needed a tracker Needed in in a testable way
  • #4 A code base, one activity, needed a tracker Needed in in a testable way
  • #6 A code base, one activity, needed a tracker Needed in in a testable way
  • #7 A code base, one activity, needed a tracker Needed in in a testable way
  • #8 A code base, one activity, needed a tracker Needed in in a testable way
  • #9 A code base, one activity, needed a tracker Needed in in a testable way
  • #33 Same as before
  • #34 Same as before
  • #37 Todo split Show inject interface TODO show singleton! But needs lazy loading then! TODO in code!
  • #40 Known as @Inject
  • #41 Todo split Show inject interface TODO show singleton! But needs lazy loading then! TODO in code!
  • #42 Todo split Show inject interface TODO show singleton! But needs lazy loading then! TODO in code!
  • #43 Todo split Show inject interface TODO show singleton! But needs lazy loading then! TODO in code!
  • #44 Todo split Show inject interface TODO show singleton! But needs lazy loading then! TODO in code!
  • #45 Todo split Show inject interface TODO show singleton! But needs lazy loading then! TODO in code!
  • #46 Todo split Show inject interface TODO show singleton! But needs lazy loading then! TODO in code!
  • #47 Todo split Show inject interface TODO show singleton! But needs lazy loading then! TODO in code!
  • #48 Same as before
  • #49 Same as before
  • #52 Same as before
  • #53 Same as before
  • #54 Same as before
  • #55 Same as before
  • #56 Same as before
  • #57 Same as before
  • #58 Same as before
  • #59 Same as before
  • #61 RobGuice
  • #62 RobGuice
  • #63 Cheated a bit with some newer reflection methods
  • #64 Google slide NOI
  • #65 Problem google said don’t use Relfection is slow!
  • #66 Problem google said don’t use Relfection is slow!
  • #67 Problem google said don’t use Relfection is slow!
  • #68 Problem google said don’t use Relfection is slow!
  • #74 Add street figher
  • #80 Not in JSR
  • #83 Google slide NOI
  • #85 Same!
  • #87 Same!
  • #91 Lets try anway
  • #92 Lets try anway
  • #117 Dagger is heavy weight, top down, annotation porn, compile safe, faster, not for unit tests TP is light weight, bottom up, reduced boilerplate, runtime bases scopes, JSR conform, testing focused
  • #118 Dagger is heavy weight, top down, annotation porn, compile safe, faster, not for unit tests TP is light weight, bottom up, reduced boilerplate, runtime bases scopes, JSR conform, testing focused
  • #119 Dagger is heavy weight, top down, annotation porn, compile safe, faster, not for unit tests TP is light weight, bottom up, reduced boilerplate, runtime bases scopes, JSR conform, testing focused
  • #120 Dagger is heavy weight, top down, annotation porn, compile safe, faster, not for unit tests TP is light weight, bottom up, reduced boilerplate, runtime bases scopes, JSR conform, testing focused