본문

170712(수) - Dependency Injection with Dagger2

Dependency Injection with Dagger2


https://github.com/codepath/android_guides/wiki/Dependency-Injection-with-Dagger-2

http://qiita.com/satorufujiwara/items/0f95ccfc3820d3ee1370

http://frogermcs.github.io/activities-multibinding-in-dagger-2/

https://github.com/aldoborrero/Dagger2MultibindingKotlin/blob/master/app/src/main/java/dagger2/recipes/multibinding/MyApplication.kt


Overview

- Android app은 다양한 object에 의존할 수 밖에 없다. 그래서 나온 대안이 DI Framework.

https://www.youtube.com/watch?v=IKD2-MAkXyQ

- 다른 DI Framework가 있지만 XML 제공에 한계가 있으며 검증 및 성능저하 문제가 있다.

- Dagger2는 "java annotation"과 "compile time" check를 통해 분석하고 DI 한다.



Advantages

- shared instances에 대한 access를 단순화

- 복잡한 dependencies의 쉬운 구성

ㆍdependency graph 제공

ㆍtrace와 이해가 쉬운 코드 생성

ㆍ많은 양의 boilerplate code를 작성하지 않아도 된다.

ㆍRefactoring 단순화에도 도움


- unit & integration testing이 쉽다.

ㆍdependency graph에 의한 모듈 교체가 간단하다.

ㆍmock 에 표현이 가능하다.


- Scoped instances

ㆍApplication 전체 lifecycle의 instance를 관리하는것은 쉽지 않다.

ㆍDagger2는 이를 지정가능



Creating Singletons

Dagger Injections Overview

- provideGson(), provideRetrofit() 등 Module의 method 명은 중요하지 않다.

- @Inject 는 inject() method를 호출하며 Activity, Fragment, Service에서 동작 가능하다.

- inject() method에는 base classes를 넣지 않는것을 권장한다. strongly type classes를 explicit 하게 사용하자.



Instantiating the component

- Dagger2 component가 constructor를 가지고 있지 않다면 create() 사용 가능

mNetComponent = com.codepath.dagger.components.DaggerNetComponent.create();


- cannot refrence Dagger* 라는 에러 발생시 rebuild 필요



Qualified types

Dagger Qualifiers

- 같은 return type이지만 다른 object가 필요하다면, @Named qualifier 사용.

@Provides @Named("cached") @Singleton OkHttpClient provideOkHttpClient(Cache cache) { OkHttpClient client = new OkHttpClient(); client.setCache(cache); return client; } @Provides @Named("non_cached") @Singleton OkHttpClient provideOkHttpClient() { OkHttpClient client = new OkHttpClient(); return client; }


@Inject @Named("cached") OkHttpClient client; @Inject @Named("non_cached") OkHttpClient client2;


- custom qualifier annotations

@Qualifier @Documented @Retention(RUNTIME) public @interface DefaultPreferences { }



Scope

Dagger Scopes

- 몇개를 만들던지 제약은 없음

- Dagger2는 runtime시 annotation에 의존하지는 않지만, RetentionPolicy를 RUNTIME으로 유지하면 나중에 modules를 검사할때 유용하다.


@Scope @Documented @Retention(value=RetentionPolicy.RUNTIME) public @interface MyActivityScope { }



Dependent Component vs. Subcomponents

- Scope를 활용하면 dependent component와 subcomponent를 만들 수 있다.

- All time remain in memory가 아닌경우 사용하면 좋다.


- Dependent components는 downstream에 inject될 수 있는지 explicitly하게 나열하기 위해 parent component를 필요로 한다. 

ㆍ Subcomponent는 위와 같지 않다.

ㆍ Parent component는 downstream component의 type과 method를 expose 해야한다.

// parent component @Singleton @Component(modules={AppModule.class, NetModule.class}) public interface NetComponent { // remove injection methods if downstream modules will perform injection // downstream components need these exposed // the method name does not matter, only the return type Retrofit retrofit(); OkHttpClient okHttpClient(); SharedPreferences sharedPreferences(); }


ㆍ 위와같이 expose하지 않으면 injection target missing 에러


ㆍ Parent component를 사용하면 explicit control과 better encapsulation 가능

ㆍ subcomponents를 사용하면 encapsulation을 줄여 쉽게 관리 가능


- Two dependent component는 같은 scope를 사용할 수 없다.

ㆍex) both @Singleton annotation

https://github.com/google/dagger/issues/107#issuecomment-71073298


- Dagger2에서는 scoped instance를 만들 수 있지만 의도된대로 동작하도록 reference를 create 및 delete해야 할 책임이 있다.


- Dependent component

Dagger Component Dependencies

ㆍdependent component는 관련된 object를 function으로 노출시켜줘야 한다.

https://github.com/codepath/dagger2-example


import java.lang.annotation.Retention; import javax.inject.Scope; @Scope public @interface UserScope { }


@Singleton @Component(modules={AppModule.class, NetModule.class}) public interface NetComponent { // downstream components need these exposed with the return type // method name does not really matter Retrofit retrofit(); }


@UserScope // using the previously defined scope, note that @Singleton will not work @Component(dependencies = NetComponent.class, modules = GitHubModule.class) public interface GitHubComponent { void inject(MainActivity activity); }


@Module public class GitHubModule { public interface GitHubApiInterface { @GET("/org/{orgName}/repos") Call<ArrayList<Repository>> getRepository(@Path("orgName") String orgName); } @Provides @UserScope // needs to be consistent with the component scope public GitHubApiInterface providesGitHubInterface(Retrofit retrofit) { return retrofit.create(GitHubApiInterface.class); } }


@Singleton @Component(modules={AppModule.class, NetModule.class}) public interface NetComponent { // remove injection methods if downstream modules will perform injection // downstream components need these exposed Retrofit retrofit(); OkHttpClient okHttpClient(); SharedPreferences sharedPreferences(); }


NetComponent mNetComponent = DaggerNetComponent.builder() .appModule(new AppModule(this)) .netModule(new NetModule("https://api.github.com")) .build(); GitHubComponent gitHubComponent = DaggerGitHubComponent.builder() .netComponent(mNetComponent) .gitHubModule(new GitHubModule()) .build();



- Subcomponents

Dagger subcomponents

ㆍcomponent object graph의 확장

ㆍ장점으로는 downstream components를 정의할 필요가 없다

ㆍsubcomponent는 parent component에서 단순히 선언만 해준다.

ㆍParent component에 factory method를 정의한다.


@MyActivityScope @Subcomponent(modules={ MyActivityModule.class }) public interface MyActivitySubComponent { @Named("my_list") ArrayAdapter myListAdapter(); }


@Module public class MyActivityModule { private final MyActivity activity; // must be instantiated with an activity public MyActivityModule(MyActivity activity) { this.activity = activity; } @Provides @MyActivityScope @Named("my_list") public ArrayAdapter providesMyListAdapter() { return new ArrayAdapter<String>(activity, android.R.layout.my_list); } ... }


@Singleton @Component(modules={ ... }) public interface MyApplicationComponent { // injection targets here // factory method to instantiate the subcomponent defined here (passing in the module instance) MyActivitySubComponent newMyActivitySubcomponent(MyActivityModule activityModule); }


public class MyActivity extends Activity { @Inject ArrayAdapter arrayAdapter; public void onCreate(Bundle savedInstance) { // assign singleton instances to fields // We need to cast to `MyApp` in order to get the right method ((MyApp) getApplication()).getApplicationComponent()) .newMyActivitySubcomponent(new MyActivityModule(this)) .inject(this); } }



- Subcomponent builders

Dagger subcomponent builders

ㆍbuilders를 사용하면 parent component에서 Factory method를 선언하지 않아도 된다.


@MyActivityScope @Subcomponent(modules={ MyActivityModule.class }) public interface MyActivitySubComponent { ... @Subcomponent.Builder interface Builder extends SubcomponentBuilder<MyActivitySubComponent> { Builder activityModule(MyActivityModule module); } } public interface SubcomponentBuilder<V> { V build(); }


@Module(subcomponents={ MyActivitySubComponent.class }) public abstract class ApplicationBinders { // Provide the builder to be included in a mapping used for creating the builders. @Binds @IntoMap @SubcomponentKey(MyActivitySubComponent.Builder.class) public abstract SubcomponentBuilder myActivity(MyActivitySubComponent.Builder impl); } @Component(modules={..., ApplicationBinders.class}) public interface ApplicationComponent { // Returns a map with all the builders mapped by their class. Map<Class<?>, Provider<SubcomponentBuilder>> subcomponentBuilders(); } // Needed only to to create the above mapping @MapKey @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface SubcomponentKey { Class<?> value(); }


public class MyActivity extends Activity { @Inject ArrayAdapter arrayAdapter; public void onCreate(Bundle savedInstance) { // assign singleton instances to fields // We need to cast to `MyApp` in order to get the right method MyActivitySubcomponent.Builder builder = (MyActivitySubcomponent.Builder) ((MyApp) getApplication()).getApplicationComponent()) .subcomponentBuilders() .get(MyActivitySubcomponent.Builder.class) .get(); builder.activityModule(new MyActivityModule(this)).build().inject(this); } }


'Mobile > DI' 카테고리의 다른 글

180423(월) - Dagger (User's Guide)  (0) 2018.04.23
170713(목) - Kotlin & Dagger2 crash  (0) 2017.07.13
170707(금) - android-architecture-todo-mvp-dagger  (0) 2017.07.07
170609(금) - DI with Dagger2  (0) 2017.06.09
170609(금) - DI with Dagger2  (0) 2017.06.09

공유

댓글