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

Dependency Injection with Dagger 2

Dependency Injection with Dagger 2

Presentation I gave at Google I/O Extended 2015 in @techspacekrk - www.meetup.com/GDG-Krakow/events/221822600/

Source code is available: https://github.com/frogermcs/GithubClient

Mirosław Stanek

May 28, 2015
Tweet

More Decks by Mirosław Stanek

Other Decks in Technology

Transcript

  1. Dependency Injection
    with Dagger 2
    Miroslaw Stanek

    View Slide

  2. about.me/froger_mcs
    @froger_mcs
    Head of mobile @ Azimo

    View Slide

  3. Why should I use DI?

    View Slide

  4. View Slide

  5. Without DI
    class UserManager {

    private ApiService apiService;

    private UserStore userStore;


    public UserManager() {

    this.apiService = new ApiSerivce();

    this.userStore = new UserStore();

    }


    void registerUser() {/* */}

    }


    class RegisterActivity extends Activity {


    private UserManager userManager;


    @Override

    protected void onCreate(Bundle b) {

    this.userManager = new UserManager();

    }


    public void onRegisterClick(View v) {

    userManager.registerUser();

    }

    }
    class UserManager {

    private ApiService apiService;

    private UserStore userStore;


    public UserManager(ApiService apiService,

    UserStore userStore) {

    this.apiService = apiService;

    this.userStore = userStore;

    }


    void registerUser() {/* */}

    }


    class RegisterActivity extends Activity {


    private UserManager userManager;


    @Override

    protected void onCreate(Bundle b) {

    ApiService api = ApiService.getInstance();

    UserStore store = UserStore.getInstance();

    this.userManager = 

    new UserManager(api, store);

    }


    public void onRegisterClick(View v) {

    userManager.registerUser();

    }

    }
    With DI

    View Slide

  6. Without DI
    class UserManager {

    private ApiService apiService;

    private UserStore userStore;


    public UserManager() {

    this.apiService = new ApiSerivce();

    this.userStore = new UserStore();

    }


    void registerUser() {/* */}

    }


    class RegisterActivity extends Activity {


    private UserManager userManager;


    @Override

    protected void onCreate(Bundle b) {

    this.userManager = new UserManager();

    }


    public void onRegisterClick(View v) {

    userManager.registerUser();

    }

    }
    class UserManager {

    private ApiService apiService;

    private UserStore userStore;


    public UserManager(ApiService apiService,

    UserStore userStore) {

    this.apiService = apiService;

    this.userStore = userStore;

    }


    void registerUser() {/* */}

    }


    class RegisterActivity extends Activity {


    private UserManager userManager;


    @Override

    protected void onCreate(Bundle b) {

    ApiService api = ApiService.getInstance();

    UserStore store = UserStore.getInstance();

    this.userManager = 

    new UserManager(api, store);

    }


    public void onRegisterClick(View v) {

    userManager.registerUser();

    }

    }
    With DI

    View Slide

  7. Without DI With DI

    View Slide

  8. Why DI is bad?

    View Slide

  9. • DI requires a lot of boilerplate
    • Code hard to trace
    • Dependency injection frameworks are slow

    View Slide

  10. Dependency Injection
    frameworks

    View Slide

  11. DI example
    LoginActivity
    LoginActivityPresenter
    UserManager
    ApiService SharedPreferences
    UserDataStore

    View Slide

  12. DI example

    (without DI framework)
    public class LoginActivity extends AppCompatActivity {


    LoginActivityPresenter presenter;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);


    OkHttpClient okHttpClient = new OkHttpClient();

    RestAdapter.Builder builder = new RestAdapter.Builder();

    builder.setClient(new OkClient(okHttpClient))

    RestAdapter restAdapter = builder.build();


    ApiService apiService = restAdapter.create(ApiService.class);

    UserManager userManager = UserManager.getInstance(apiService);

    UserDataStore userDataStore = UserDataStore.getInstance(

    getSharedPreferences("prefs", MODE_PRIVATE)

    );


    presenter = new LoginActivityPresenter(this, userManager, userDataStore);

    }

    }

    View Slide

  13. DI example
    LoginActivity
    LoginActivityPresenter
    UserManager
    ApiService SharedPreferences
    UserDataStore
    RegisterActivity
    RegisterActivityPresenter
    UserManager
    ApiService SharedPreferences
    UserDataStore

    View Slide

  14. DI example

    (without Dagger)
    public class LoginActivity extends AppCompatActivity {


    LoginActivityPresenter presenter;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);


    OkHttpClient okHttpClient = new OkHttpClient();

    RestAdapter.Builder builder = new
    RestAdapter.Builder();

    builder.setClient(new OkClient(okHttpClient))

    RestAdapter restAdapter = builder.build();


    ApiService apiService =
    restAdapter.create(ApiService.class);

    UserManager userManager =
    UserManager.getInstance(apiService);

    UserDataStore userDataStore =
    public class RegisterActivity extends AppCompatActivity {


    RegisterActivityPresenter presenter;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);


    OkHttpClient okHttpClient = new OkHttpClient();

    RestAdapter.Builder builder = new RestAdapter.Builder();

    builder.setClient(new OkClient(okHttpClient))

    RestAdapter restAdapter = builder.build();


    ApiService apiService = restAdapter.create(ApiService.class);

    UserManager userManager = UserManager.getInstance(apiService);

    UserDataStore userDataStore = UserDataStore.getInstance(

    getSharedPreferences("prefs", MODE_PRIVATE)

    );


    presenter = new RegisterActivityPresenter(this, userManager,
    userDataStore);

    }

    }
    2x

    View Slide

  15. DI example

    public class LoginActivity extends AppCompatActivity {


    @Inject

    LoginActivityPresenter presenter;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    getDependenciesGraph().inject(this);

    }

    }

    View Slide

  16. DI example

    (with Dagger 2)
    public class LoginActivity extends AppCompatActivity {


    @Inject

    LoginActivityPresenter presenter;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    DaggerSplashActivityComponent.builder()

    .appComponent(getAppComponent())

    .splashActivityModule(new SplashActivityModule(this))

    .build()

    .inject(this);

    }

    }

    View Slide

  17. DI example

    (with Dagger 2)
    public class LoginActivity extends AppCompatActivity {


    @Inject

    LoginActivityPresenter presenter;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    DaggerSplashActivityComponent.builder()

    .appComponent(getAppComponent())

    .splashActivityModule(new SplashActivityModule(this))

    .build()

    .inject(this);

    }

    }

    View Slide

  18. No more instantiation.

    View Slide

  19. DI framework cons
    (historical perspective)

    View Slide

  20. • Performance (reflection usage)
    • Runtime graph validation
    • Instantiation magic (no source code)

    View Slide

  21. Dagger 1
    All problems gone!

    View Slide

  22. • No reflection in object instantiation
    • Compile-time validation and instantiation
    • Source code for inject adapters

    View Slide

  23. Almost all problems
    gone…

    View Slide

  24. Dagger 1 flaws

    View Slide

  25. Generated source code
    @Singleton

    public class AnalyticsManager {


    private Application app;


    @Inject

    public AnalyticsManager(Application app) {

    this.app = app;

    }

    }

    View Slide

  26. Generated source code
    public final class AnalyticsManager$$InjectAdapter extends Binding

    implements Provider {

    private Binding app;


    public AnalyticsManager$$InjectAdapter() {

    super("frogermcs.io.githubclient.utils.AnalyticsManager", "members/
    frogermcs.io.githubclient.utils.AnalyticsManager", IS_SINGLETON,
    AnalyticsManager.class);

    }


    @Override

    @SuppressWarnings("unchecked")

    public void attach(Linker linker) {

    app = (Binding)
    linker.requestBinding("android.app.Application", AnalyticsManager.class,
    getClass().getClassLoader());

    }


    @Override

    public void getDependencies(Set> getBindings, Set>
    injectMembersBindings) {

    getBindings.add(app);

    }


    @Override

    public AnalyticsManager get() {

    AnalyticsManager result = new AnalyticsManager(app.get());

    return result;

    }

    }

    View Slide

  27. Proguard
    -dontwarn dagger.internal.codegen.**

    -keepclassmembers,allowobfuscation class * {

    @javax.inject.* *;

    @dagger.* *;

    ();

    }

    -keep class dagger.* { *; }

    -keep class javax.inject.* { *; }

    -keep class * extends dagger.internal.Binding

    -keep class * extends dagger.internal.ModuleAdapter

    -keep class * extends dagger.internal.StaticInjection

    View Slide

  28. Proguard
    Process: frogermcs.io.githubclient, PID: 32685
    java.lang.RuntimeException: Unable to create application frogermcs.io.githubclient.App:
    java.lang.IllegalStateException: Module adapter for class frogermcs.io.githubclient.app.b
    could not be loaded. Please ensure that code generation was run for this module.
    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4556)

    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
    Caused by: java.lang.IllegalStateException: Module adapter for class
    frogermcs.io.githubclient.b could not be loaded. Please ensure that code generation was run
    for this module.
    at dagger.internal.h.a(Unknown Source)
    at dagger.internal.h.a(Unknown Source)
    at dagger.internal.r.b(Unknown Source)
    at dagger.internal.g.a(Unknown Source)
    at dagger.internal.t.a(Unknown Source)

    View Slide

  29. Reflection, Runtime graph
    composition
    • „Reflection is used to figure out how everything
    fits together”


    (DAGGER 2 - A New Type of dependency injection - https://www.youtube.com/watch?v=oK_XtfXPkqw )
    • Runtime graph composition

    View Slide

  30. Dagger 1 API
    public abstract class ObjectGraph {

    public static ObjectGraph create(Object... modules);

    public abstract ObjectGraph plus(Object... modules);

    public abstract T get(Class type);

    public abstract T inject(T instance);

    public abstract void validate();

    public abstract void injectStatics();

    }


    public @interface Module {

    Class>[] injects() default { };

    Class>[] staticInjections() default { };

    Class>[] includes() default { };

    Class> addsTo() default Void.class;

    boolean overrides() default false;

    boolean complete() default true;

    boolean library() default false;

    }


    public @interface Provides {

    }


    public interface Lazy {

    T get();

    }


    public interface MembersInjector {

    void injectMembers(T instance);

    }

    View Slide

  31. Dagger 2
    Silver bullet?

    View Slide

  32. Dagger 2
    • 100% Proguard friendly
    • Generates fully traceable code
    • Easy to read generated code (as close to hand-
    written code as possible)
    • Performance

    View Slide

  33. Dagger 2 API
    public @interface Component {

    Class>[] modules() default {};

    Class>[] dependencies() default {};

    }


    public @interface Subcomponent {

    Class>[] modules() default {};

    }


    public @interface Module {

    Class>[] includes() default {};

    }


    public @interface Provides {

    }


    public @interface MapKey {

    boolean unwrapValue() default true;

    }


    public interface Lazy {

    T get();

    }

    View Slide

  34. Dagger 1 API
    public abstract class ObjectGraph {

    public static ObjectGraph create(Object... modules);

    public abstract ObjectGraph plus(Object... modules);

    public abstract T get(Class type);

    public abstract T inject(T instance);

    public abstract void validate();

    public abstract void injectStatics();

    }


    public @interface Module {

    Class>[] injects() default { };

    Class>[] staticInjections() default { };

    Class>[] includes() default { };

    Class> addsTo() default Void.class;

    boolean overrides() default false;

    boolean complete() default true;

    boolean library() default false;

    }


    public @interface Provides {

    }


    public interface Lazy {

    T get();

    }


    public interface MembersInjector {

    void injectMembers(T instance);

    }

    View Slide

  35. DI with Dagger 2
    fundamentals

    View Slide

  36. @Inject
    JSR-330

    View Slide

  37. @Inject

    (constructor injection)
    public class LoginActivityPresenter {

    private LoginActivity loginActivity;

    private UserDataStore userDataStore;

    private UserManager userManager;


    @Inject

    public LoginActivityPresenter(LoginActivity loginActivity, 

    UserDataStore userDataStore, 

    UserManager userManager) {

    this.loginActivity = loginActivity;

    this.userDataStore = userDataStore;

    this.userManager = userManager;

    }

    }
    JSR-330

    View Slide

  38. @Inject

    (fields injection)
    public class LoginActivity extends AppCompatActivity {


    @Inject

    LoginActivityPresenter presenter;

    @Inject

    AnalyticsManager analyticsManager;

    @Override

    protected void onCreate(Bundle bundle) {

    super.onCreate(bundle);

    getAppComponent().inject(this);

    }

    }
    JSR-330

    View Slide

  39. @Inject

    (method injection)
    public class LoginActivityPresenter {

    private LoginActivity loginActivity;


    @Inject

    public LoginActivityPresenter(LoginActivity loginActivity) {

    this.loginActivity = loginActivity;

    this.userDataStore = userDataStore;

    }
    @Inject

    public void enableWatches(Watches watches) {

    watches.register(this);

    }

    }
    JSR-330

    View Slide

  40. @Module
    @Module

    public class GithubApiModule {


    @Provides

    @Singleton

    OkHttpClient provideOkHttpClient() {

    OkHttpClient okHttpClient = new OkHttpClient();

    okHttpClient.setConnectTimeout(60 * 1000, TimeUnit.MILLISECONDS);

    okHttpClient.setReadTimeout(60 * 1000, TimeUnit.MILLISECONDS);

    return okHttpClient;

    }


    @Provides

    @Singleton

    RestAdapter provideRestAdapter(Application application, OkHttpClient okHttpClient) {

    RestAdapter.Builder builder = new RestAdapter.Builder();

    builder.setClient(new OkClient(okHttpClient))

    .setEndpoint(application.getString(R.string.endpoint));

    return builder.build();

    }

    }
    Dagger 2

    View Slide

  41. @Provide
    Dagger 2
    @Module

    public class GithubApiModule {


    @Provides

    @Singleton

    OkHttpClient provideOkHttpClient() {

    OkHttpClient okHttpClient = new OkHttpClient();

    okHttpClient.setConnectTimeout(60 * 1000, TimeUnit.MILLISECONDS);

    okHttpClient.setReadTimeout(60 * 1000, TimeUnit.MILLISECONDS);

    return okHttpClient;

    }


    @Provides

    @Singleton

    RestAdapter provideRestAdapter(Application application, OkHttpClient okHttpClient) {

    RestAdapter.Builder builder = new RestAdapter.Builder();

    builder.setClient(new OkClient(okHttpClient))

    .setEndpoint(application.getString(R.string.endpoint));

    return builder.build();

    }

    }

    View Slide

  42. @Component
    @Singleton

    @Component(

    modules = {

    AppModule.class,

    GithubApiModule.class

    }

    )

    public interface AppComponent {

    void inject(GithubClientApplication githubClientApplication);


    Application getApplication();


    AnalyticsManager getAnalyticsManager();


    UserManager getUserManager();


    }
    Dagger 2

    View Slide

  43. @Component
    @ActivityScope

    @Component(

    modules = SplashActivityModule.class,

    dependencies = AppComponent.class

    )

    public interface SplashActivityComponent {

    SplashActivity inject(SplashActivity splashActivity);


    SplashActivityPresenter presenter();

    }
    Dagger 2

    View Slide

  44. @Scope
    @Scope

    public @interface PerActivity {

    }
    JSR-330
    Scoping example
    http://fernandocejas.com/2015/04/11/tasting-
    dagger-2-on-android/
    Implementation

    View Slide

  45. @MapKey
    @MapKey(unwrapValue = true)

    @interface TestKey {

    String value();

    }
    @Provides(type = Type.MAP)

    @TestKey("foo")

    String provideFooKey() {

    return "foo value";

    }


    @Provides(type = Type.MAP)

    @TestKey("bar")

    String provideBarKey() {

    return "bar value";

    }
    @Inject

    Map map;
    map.toString() => „{foo=foo value, bar=bar value}”
    Dagger 2
    Definition Usage

    View Slide

  46. @Qualifier
    @Provides

    @Singleton

    @GithubRestAdapter //Qualifier

    RestAdapter provideRestAdapter() {

    return new RestAdapter.Builder()

    .setEndpoint("https://api.github.com")

    .build();

    }


    @Provides

    @Singleton

    @FacebookRestAdapter //Qualifier

    RestAdapter provideRestAdapter() {

    return new RestAdapter.Builder()

    .setEndpoint("https://api.facebook.com")

    .build();

    }
    JSR-330

    View Slide

  47. @Qualifier
    @Inject

    @GithubRestAdapter

    RestAdapter githubRestAdapter;


    @Inject

    @FacebookRestAdapter

    RestAdapter facebookRestAdapter;
    JSR-330

    View Slide

  48. Dagger 2 API
    public @interface Component {

    Class>[] modules() default {};

    Class>[] dependencies() default {};

    }


    public @interface Subcomponent {

    Class>[] modules() default {};

    }


    public @interface Module {

    Class>[] includes() default {};

    }


    public @interface Provides {

    }


    public @interface MapKey {

    boolean unwrapValue() default true;

    }


    public interface Lazy {

    T get();

    }

    View Slide

  49. App example

    View Slide

  50. View Slide

  51. View Slide

  52. View Slide

  53. Source code
    https://github.com/frogermcs/GithubClient

    View Slide

  54. Problems & doubts

    View Slide

  55. • Scoping
    • Testing 

    https://github.com/google/dagger/issues/110
    • Dagger 1 vs Dagger 2

    View Slide

  56. QA

    View Slide

  57. • GithubClient source code

    https://github.com/frogermcs/GithubClient
    • DAGGER 2 - A New Type of dependency
    injection

    https://www.youtube.com/watch?v=oK_XtfXPkqw
    • Dagger 1 to 2 migration process

    http://frogermcs.github.io/dagger-1-to-2-migration/
    • The Future of Dependency Injection with
    Dagger 2

    https://www.parleys.com/tutorial/the-future-dependency-injection-
    dagger-2

    View Slide

  58. View Slide