Asynchronous Dependency Injection

A4ccb38897d103985c2e5c5eae1b4317?s=47 Eric Leong
September 26, 2017

Asynchronous Dependency Injection

Dependency injection is a useful pattern for writing modular code, but by design it requires an object's dependencies to created first, which adversely impacts your app's startup time. One solution is to use asynchronous dependency injection to create dependencies on background threads, in parallel. There are two main strategies for accomplishing this goal: Dagger Producers and RxJava with Dagger. This talk will focus on migrating slow dependencies to Dagger Producers to improve app performance.

Code: https://github.com/ericleong/bentobox
Slides: http://ericleong.me/talks/asynchronous-dependency-injection
Video: https://www.youtube.com/watch?v=c6fj_davikM

A4ccb38897d103985c2e5c5eae1b4317?s=128

Eric Leong

September 26, 2017
Tweet

Transcript

  1. Asynchronous Dependency Injection Eric Leong Android Engineer, Tumblr

  2. None
  3. new new

  4. None
  5. Why Dependency Injection?

  6. Why Dependency Injection? ()

  7. None
  8. None
  9. @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); } }
  10. 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(); } }
  11. None
  12. None
  13. None
  14. Lazy Dependency Injection

  15. Lazy<>

  16. None
  17. ❌ ⏳

  18. Asynchronous Dependency Injection

  19. Main Thread Side Thread

  20. Main Thread Side Thread

  21. ⏳ ⏳

  22. ⏳ ⏳

  23. Main Thread Side Thread

  24. None
  25. 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); } }
  26. RxJava 2 @Inject Lazy<Salad> saladLazy; Observable.fromCallable( new Callable<Salad>() { public

    Salad call() throws Exception { return saladLazy.get(); } }) .subscribe(new Consumer<Salad>() { public void accept(Salad salad) throws Exception { salad.dress(); } });
  27. RxJava 2 @Module public class SaladModule { @Singleton @Provides Observable<Salad>

    providesSaladObservable(final Lazy<Salad> saladLazy) { return Observable.fromCallable(new Callable<Salad>() { @Override public Salad call() throws Exception { return saladLazy.get(); } }); } }
  28. Observable<> @Inject Observable<Salad> saladObservable; saladObservable .subscribeOn(Schedulers.computation()) .subscribe(new Consumer<Salad>() { @Override

    public void accept(Salad salad) throws Exception { salad.swim(); } });
  29. Dagger Producers an extension to Dagger that implements asynchronous dependency

    injection
  30. 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); } }
  31. Dagger 2 Producers @ProductionComponent( dependencies = AppComponent.class, modules = {

    ExecutorModule.class, SaladModule.class }) public interface AppProductionComponent { ListenableFuture<Salad> getSalad(); }
  32. ListenableFuture<> App app = (App) getApplicationContext(); AppProductionComponent productionComponent = app.getAppProductionComponent();

    ListenableFuture<Salad> saladFuture = productionComponent.getSalad(); Futures.addCallback(saladFuture, new FutureCallback<Salad>() { @Override public void onSuccess(@Nullable Salad salad) { if (salad != null) { salad.dress(); } } });
  33. Dagger 2 Production Thread(s) @Module public class ExecutorModule { @Provides

    @Production static Executor executor() { return Executors.newCachedThreadPool(); } }
  34. None
  35. None
  36. None
  37. None
  38. ⏳ ⏳ @Provides @Produces

  39. ⏳ ⏳ @Provides can’t depend on @Produces

  40. ⏳ ⏳ ⏳ ⏳ @Provides @Produces Solution 1

  41. ⏳ ⏳ ⏳ @Provides @Produces Solution 2 ⏳

  42. None
  43. Deferring Until Ready public class DeferredLogger implements Logger { final

    ListenableFuture<Logger> loggerFuture; @Override public void log(final String string) { Futures.addCallback(loggerFuture, new FutureCallback<Logger>() { @Override public void onSuccess(@Nullable Logger logger) { logger.log(string); } }, MoreExecutors.directExecutor()); } }
  44. Deferring Until Ready @Module public class LoggerModule { @Provides Logger

    providesLogger(App app) { AppProductionComponent productionComponent; productionComponent = app.getAppProductionComponent(); return new DeferredLogger(productionComponent.getLogger()); } }
  45. LazyListenableFuture public class LazyListenableFuture<T> implements Lazy<T> { private final ListenableFuture<T>

    listenableFuture; @Override public T get() { try { return listenableFuture.get(); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException("Could not get listenable.", e); } } }
  46. Should you use asynchronous dependency injection?

  47. None
  48. 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
  49. 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
  50. None
  51. Asynchronous Dependency Injection is a last resort.

  52. @ericwleong github.com/ericleong/bentobox