$30 off During Our Annual Pro Sale. View Details »

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. Asynchronous
    Dependency Injection
    Eric Leong
    Android Engineer, Tumblr

    View Slide

  2. View Slide



  3. new

    new

    View Slide

  4. View Slide

  5. Why Dependency Injection?



    View Slide

  6. Why Dependency Injection?



    ()

    View Slide

  7. View Slide

  8. View Slide

  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);
    }
    }

    View Slide

  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();
    }
    }

    View Slide

  11. View Slide

  12. View Slide

  13. View Slide

  14. Lazy
    Dependency Injection

    View Slide




  15. Lazy<>

    View Slide

  16. View Slide





  17. View Slide

  18. Asynchronous
    Dependency Injection

    View Slide

  19. Main Thread
    Side Thread





    View Slide






  20. Main Thread
    Side Thread

    View Slide











  21. View Slide








  22. View Slide



  23. Main Thread
    Side Thread



    View Slide

  24. View Slide

  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);
    }
    }

    View Slide

  26. 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();
    }
    });

    View Slide

  27. 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();
    }
    });
    }
    }

    View Slide

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

    View Slide

  29. Dagger Producers
    an extension to Dagger that
    implements asynchronous
    dependency injection

    View Slide

  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);
    }
    }

    View Slide

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

    View Slide

  32. 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();
    }
    }
    });

    View Slide

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

    View Slide

  34. View Slide

  35. View Slide

  36. View Slide

  37. View Slide






  38. @Provides
    @Produces


    View Slide








  39. @Provides can’t
    depend on @Produces

    View Slide









  40. @Provides
    @Produces


    Solution 1

    View Slide






  41. @Provides
    @Produces


    Solution 2


    View Slide

  42. View Slide

  43. 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());
    }
    }

    View Slide

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

    View Slide

  45. 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);
    }
    }
    }

    View Slide

  46. Should you use asynchronous dependency injection?

    View Slide

  47. View Slide

  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

    View Slide

  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

    View Slide

  50. View Slide

  51. Asynchronous
    Dependency Injection is
    a last resort.

    View Slide


  52. @ericwleong
    github.com/ericleong/bentobox

    View Slide