본문
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/
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
- 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
- 같은 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
- 몇개를 만들던지 제약은 없음
- 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
ㆍ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
ㆍ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
ㆍ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 |
댓글