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

    View full-size slide

  2. Why Dependency Injection?



    View full-size slide

  3. Why Dependency Injection?



    ()

    View full-size slide

  4. @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 full-size slide

  5. 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 full-size slide

  6. Lazy
    Dependency Injection

    View full-size slide

  7. Asynchronous
    Dependency Injection

    View full-size slide

  8. Main Thread
    Side Thread





    View full-size slide






  9. Main Thread
    Side Thread

    View full-size slide



  10. Main Thread
    Side Thread



    View full-size slide

  11. 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 full-size slide

  12. 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 full-size slide

  13. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  16. 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 full-size slide

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

    View full-size slide

  18. 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 full-size slide

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

    View full-size slide






  20. @Provides
    @Produces


    View full-size slide








  21. @Provides can’t
    depend on @Produces

    View full-size slide









  22. @Provides
    @Produces


    Solution 1

    View full-size slide






  23. @Provides
    @Produces


    Solution 2


    View full-size slide

  24. 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 full-size slide

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

    View full-size slide

  26. 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 full-size slide

  27. Should you use asynchronous dependency injection?

    View full-size slide

  28. 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 full-size slide

  29. 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 full-size slide

  30. Asynchronous
    Dependency Injection is
    a last resort.

    View full-size slide


  31. @ericwleong
    github.com/ericleong/bentobox

    View full-size slide