Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Asynchronous Dependency Injection

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

Eric Leong

September 26, 2017
Tweet

More Decks by Eric Leong

Other Decks in Programming

Transcript

  1. @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); } }
  2. 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(); } }
  3. 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); } }
  4. 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(); } });
  5. 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(); } }); } }
  6. 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); } }
  7. Dagger 2 Producers @ProductionComponent( dependencies = AppComponent.class, modules = {

    ExecutorModule.class, SaladModule.class }) public interface AppProductionComponent { ListenableFuture<Salad> getSalad(); }
  8. 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(); } } });
  9. Dagger 2 Production Thread(s) @Module public class ExecutorModule { @Provides

    @Production static Executor executor() { return Executors.newCachedThreadPool(); } }
  10. 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()); } }
  11. Deferring Until Ready @Module public class LoggerModule { @Provides Logger

    providesLogger(App app) { AppProductionComponent productionComponent; productionComponent = app.getAppProductionComponent(); return new DeferredLogger(productionComponent.getLogger()); } }
  12. 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); } } }
  13. 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
  14. 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