Slide 1

Slide 1 text

Asynchronous Dependency Injection Eric Leong Android Engineer, Tumblr

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

new new

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Why Dependency Injection?

Slide 6

Slide 6 text

Why Dependency Injection? ()

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

@Module public class SushiModule { @Provides Fish providesFish() { return new Fish(); } @Provides Rice providesRice() { return new Rice(); } @Provides Sushi providesSushi(Fish fish, Rice rice) { return new Sushi(fish, rice); } }

Slide 10

Slide 10 text

public class BentoBoxActivity extends Activity { @Inject Sushi sushi; @Override protected void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); setContentView(R.layout.activity_bento_box); sushi.swim(); } }

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

Lazy Dependency Injection

Slide 15

Slide 15 text

Lazy<>

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

❌ ⏳

Slide 18

Slide 18 text

Asynchronous Dependency Injection

Slide 19

Slide 19 text

Main Thread Side Thread

Slide 20

Slide 20 text

Main Thread Side Thread

Slide 21

Slide 21 text

⏳ ⏳

Slide 22

Slide 22 text

⏳ ⏳

Slide 23

Slide 23 text

Main Thread Side Thread

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

Dagger 2 @Component(modules = { AndroidBindingModule.class, SaladModule.class }) public interface AppComponent { void inject(App app); } @Module public class SaladModule { @Provides Salad salad(Cucumber c, Tomato t) { return new Salad(c, t); } }

Slide 26

Slide 26 text

RxJava 2 @Inject Lazy saladLazy; Observable.fromCallable( new Callable() { public Salad call() throws Exception { return saladLazy.get(); } }) .subscribe(new Consumer() { public void accept(Salad salad) throws Exception { salad.dress(); } });

Slide 27

Slide 27 text

RxJava 2 @Module public class SaladModule { @Singleton @Provides Observable providesSaladObservable(final Lazy saladLazy) { return Observable.fromCallable(new Callable() { @Override public Salad call() throws Exception { return saladLazy.get(); } }); } }

Slide 28

Slide 28 text

Observable<> @Inject Observable saladObservable; saladObservable .subscribeOn(Schedulers.computation()) .subscribe(new Consumer() { @Override public void accept(Salad salad) throws Exception { salad.swim(); } });

Slide 29

Slide 29 text

Dagger Producers an extension to Dagger that implements asynchronous dependency injection

Slide 30

Slide 30 text

Dagger 2 Producers @Module public class SaladModule { @Provides Salad salad(Cucumber c, Tomato t) { return new Salad(c, t); } } @ProducerModule public class SaladModule { @Produces Salad salad(Cucumber c, Tomato t) { return new Salad(c, t); } }

Slide 31

Slide 31 text

Dagger 2 Producers @ProductionComponent( dependencies = AppComponent.class, modules = { ExecutorModule.class, SaladModule.class }) public interface AppProductionComponent { ListenableFuture getSalad(); }

Slide 32

Slide 32 text

ListenableFuture<> App app = (App) getApplicationContext(); AppProductionComponent productionComponent = app.getAppProductionComponent(); ListenableFuture saladFuture = productionComponent.getSalad(); Futures.addCallback(saladFuture, new FutureCallback() { @Override public void onSuccess(@Nullable Salad salad) { if (salad != null) { salad.dress(); } } });

Slide 33

Slide 33 text

Dagger 2 Production Thread(s) @Module public class ExecutorModule { @Provides @Production static Executor executor() { return Executors.newCachedThreadPool(); } }

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

⏳ ⏳ @Provides @Produces

Slide 39

Slide 39 text

⏳ ⏳ @Provides can’t depend on @Produces

Slide 40

Slide 40 text

⏳ ⏳ ⏳ ⏳ @Provides @Produces Solution 1

Slide 41

Slide 41 text

⏳ ⏳ ⏳ @Provides @Produces Solution 2 ⏳

Slide 42

Slide 42 text

No content

Slide 43

Slide 43 text

Deferring Until Ready public class DeferredLogger implements Logger { final ListenableFuture loggerFuture; @Override public void log(final String string) { Futures.addCallback(loggerFuture, new FutureCallback() { @Override public void onSuccess(@Nullable Logger logger) { logger.log(string); } }, MoreExecutors.directExecutor()); } }

Slide 44

Slide 44 text

Deferring Until Ready @Module public class LoggerModule { @Provides Logger providesLogger(App app) { AppProductionComponent productionComponent; productionComponent = app.getAppProductionComponent(); return new DeferredLogger(productionComponent.getLogger()); } }

Slide 45

Slide 45 text

LazyListenableFuture public class LazyListenableFuture implements Lazy { private final ListenableFuture listenableFuture; @Override public T get() { try { return listenableFuture.get(); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException("Could not get listenable.", e); } } }

Slide 46

Slide 46 text

Should you use asynchronous dependency injection?

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

Reasons not to use asynchronous injection ● Mental overhead of asynchronous dependencies ● Many libraries can’t be asynchronous ● Dependency tree is likely to be deep, not wide

Slide 49

Slide 49 text

Example: Fresco with OkHttp 1. Fresco depends on OkHttp 2.SimpleDraweeView requires Fresco initialization 3. Activity layout requires SimpleDraweeView Therefore… 1. OkHttp must be initialized before Activity.setContentView() 2. Main thread is blocked by OkHttp

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

Asynchronous Dependency Injection is a last resort.

Slide 52

Slide 52 text

@ericwleong github.com/ericleong/bentobox