Share
Embed
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
Transcript
58 slides
-
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 -
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 -
• DI requires a lot of boilerplate • Code hard
to trace • Dependency injection frameworks are slow -
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); } } -
DI example LoginActivity LoginActivityPresenter UserManager ApiService SharedPreferences UserDataStore RegisterActivity RegisterActivityPresenter
UserManager ApiService SharedPreferences UserDataStore -
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 -
DI example public class LoginActivity extends AppCompatActivity { @Inject
LoginActivityPresenter presenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getDependenciesGraph().inject(this); } } -
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); } } -
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); } } -
• Performance (reflection usage) • Runtime graph validation • Instantiation
magic (no source code) -
• No reflection in object instantiation • Compile-time validation and
instantiation • Source code for inject adapters -
Generated source code @Singleton public class AnalyticsManager { private
Application app; @Inject public AnalyticsManager(Application app) { this.app = app; } } -
Generated source code public final class AnalyticsManager$$InjectAdapter extends Binding<AnalyticsManager> implements
Provider<AnalyticsManager> { private Binding<android.app.Application> 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<android.app.Application>) linker.requestBinding("android.app.Application", AnalyticsManager.class, getClass().getClassLoader()); } @Override public void getDependencies(Set<Binding<?>> getBindings, Set<Binding<?>> injectMembersBindings) { getBindings.add(app); } @Override public AnalyticsManager get() { AnalyticsManager result = new AnalyticsManager(app.get()); return result; } } -
Proguard -dontwarn dagger.internal.codegen.** -keepclassmembers,allowobfuscation class * { @javax.inject.* *;
@dagger.* *; <init>(); } -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 -
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) -
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 -
Dagger 1 API public abstract class ObjectGraph { public static
ObjectGraph create(Object... modules); public abstract ObjectGraph plus(Object... modules); public abstract <T> T get(Class<T> type); public abstract <T> 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> { T get(); } public interface MembersInjector<T> { void injectMembers(T instance); } -
Dagger 2 • 100% Proguard friendly • Generates fully traceable
code • Easy to read generated code (as close to hand- written code as possible) • Performance -
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> { T get(); } -
Dagger 1 API public abstract class ObjectGraph { public static
ObjectGraph create(Object... modules); public abstract ObjectGraph plus(Object... modules); public abstract <T> T get(Class<T> type); public abstract <T> 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> { T get(); } public interface MembersInjector<T> { void injectMembers(T instance); } -
@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 -
@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 -
@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 -
@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 -
@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(); } } -
@Component @Singleton @Component( modules = { AppModule.class, GithubApiModule.class } )
public interface AppComponent { void inject(GithubClientApplication githubClientApplication); Application getApplication(); AnalyticsManager getAnalyticsManager(); UserManager getUserManager(); } Dagger 2 -
@Component @ActivityScope @Component( modules = SplashActivityModule.class, dependencies = AppComponent.class )
public interface SplashActivityComponent { SplashActivity inject(SplashActivity splashActivity); SplashActivityPresenter presenter(); } Dagger 2 -
@Scope @Scope public @interface PerActivity { } JSR-330 Scoping
example http://fernandocejas.com/2015/04/11/tasting- dagger-2-on-android/ Implementation -
@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<String, String> map; map.toString() => „{foo=foo value, bar=bar value}” Dagger 2 Definition Usage -
@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 -
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> { T get(); } -
• 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