Demystifying dependency Injection: Dagger and Toothpick
-
Upload
danny-preussler -
Category
Technology
-
view
769 -
download
4
Transcript of Demystifying dependency Injection: Dagger and Toothpick
I come with knifesA story of Daggers and Toothpicks…
Droidcon Krakow 2016Danny 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 supportTracking
@PreusslerBerlin
The journey to testability starts
@PreusslerBerlin
Starring
@PreusslerBerlin
Tracker as componentpublic interface Tracker {
void trackStarted();}
@PreusslerBerlin
GoogleAnalyticsTracker as Trackerpublic class GoogleAnalyticsTracker implements Tracker {
@Override public void trackStarted() { }}
@PreusslerBerlin
PrologThe 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 IThe 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;
@Beforepublic void setup() {… Dependencies.instance = mock(Dependencies.class); when(Dependencies.instance.getTracker())
.thenReturn(tracker);}
@Testpublic void should_track() { new SimpleActivity().onCreate(null); verify(tracker).trackStarted();}
@PreusslerBerlin
@Mock Tracker tracker;
@Beforepublic void setup() {… Dependencies.instance = mock(Dependencies.class); when(Dependencies.instance.getTracker())
.thenReturn(tracker);}
@Testpublic void should_track() { new SimpleActivity().onCreate(null); verify(tracker).trackStarted();}
@PreusslerBerlin
@Mock Tracker tracker;
@Beforepublic void setup() {… Dependencies.instance = mock(Dependencies.class); when(Dependencies.instance.getTracker())
.thenReturn(tracker);}
@Testpublic void should_track() { new SimpleActivity().onCreate(null); verify(tracker).trackStarted();}
@PreusslerBerlin
+/- Somehow testable- Not very dynamic
- Lots of code
@PreusslerBerlin
Act IIThe 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 IIIReflection(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 IVCompiler 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
@Modulestatic 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;
@Rulepublic ToothPickRule toothPickRule =
new ToothPickRule(this, "APPLICATION_SCOPE");
@Testpublic void should_track() {
new LonelyActivity().onCreate(null); verify(tracker).trackStarted();}
@PreusslerBerlin
@Mock Tracker tracker;
@Rulepublic ToothPickRule toothPickRule =
new ToothPickRule(this, "APPLICATION_SCOPE");
@Testpublic 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
@Modulestatic 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!
A Be
tter T
ool b
y Th
e M
arm
ot, C
C by
2.0
, flic
kr.c
om/p
hoto
s/th
emar
mot
/628
3203
163
@PreusslerBerlin
github.com/dpreussler/
understand_dependencies
@PreusslerBerlin
TP written byStephane Nicolas
Daniel Molinero Reguera
Presented byDanny Preussler
Special thanks toFlorina Muntenescu
Thank you!
VIACOM InternationalWe are hiring