본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[android] Dagger2 tutorial part2

by 돼지왕 왕돼지 2018. 12. 5.
반응형

 

@Named, @PerActivity, @scope, @Qualifier, annotation processor, automatic field inject, automatic field injection, constructor injection, custom annotation, Dagger1, dagger1 vs dagger2, dagger2, dependency connector, dependency consumer, dependency injection, Dependency Injection in Android, dependency producer, dependency 한정, field injection, gradle setting, inject method, injection 순서, inversion of control, Method Injection, private field inject, Reflection, [android] Dagger2 tutorial part2, 의존 한정

Dependency Injection in Android

https://blog.mindorks.com/introduction-to-dagger-2-using-dependency-injection-in-android-part-1-223289c2a01b

 

-

Dependency Injection = Inversion of Control

 

 

-

한 java class 에서 다른 java class 를 new operator 로 생성하면 test 하는 것이 쉽지 않다.

이를 hard dependency 라고 부른다.

 

 

-

Dagger1 은 reflection 을 사용해서 DI 를 수행했었다.

이는 많은 단점을 가지는데, 우선 느리고, 두번째는 작업이 runtime 에서 수행이 되며, 세번째는 예상치 못한 crash 를 발생시키기 쉽다.

 

 

-

Dagger1 의 reflection 방식의 단점 보완을 위해 나온 것이 compile time 에 code generation 을 하는 dagger2 이다.

이 녀석은 annotation processor 을 이용하여 build time 에 코드 생성을 한다.

생성된 코드는 심지어 human-redability 가 강하다.

 

 

-

Injection 의 종류는 아래와 같다.

 

Constructor injection

Field injection ( private 이면 안된다 )

Method injection

 

 

-

Injection 은 아래 순서로 수행된다.

 

Constructor -> Field -> Method

 

위와 같은 순서로 inject 되기 때문에 inject 되는 field 나 method 는 constructor 에서 사용할 수 없다.

 

 

-

Dependency 를 받는 쪽(consumer)에서는 Dependency 제공자에게(producer) inject 해줄 것을 Dependency connector 를 통해 요청한다.

 

1. Dependency provider 는 @Module 로 annotate 되며, @Provides 로 annotate 된 method 들을 가지고 있다.

@Provides 로 마킹된 method 들의 return 값이 inject 된다.

 

2. Dependency consumer 는 @Inject 로 annotate 된다.

 

3. Consumer 와 Producer connect 는 @Component 로 annotate 된 interface 가 맡는다. 이 connection class 는 Dagger 에 의해 생성된다.

 

 

-

Dagger2 의 한계

 

1. Dagger2 는 자동으로 field 에 inject 하지 않는다.

2. private field 에 inject 할 수 없다.

3. field injection 을 하려면, @Component annotated 된 interface 에 inject 하려고 하는 객체를 받는 method 를 정의해야 한다.

 

 

 

 

Dependency Injection in Android #2

https://blog.mindorks.com/introduction-to-dagger-2-using-dependency-injection-in-android-part-2-b55857911bcd

 

-

dagger 사용을 위해서는 아래와 같은 gradle setting 이 필요하다.

dependencies { 

    … 

    compile "com.google.dagger:dagger:2.8”

    annotationProcessor "com.google.dagger:dagger-compiler:2.8” 

    provided 'javax.annotation:jsr250-api:1.0’ 

    compile 'javax.inject:javax.inject:1’

}

 

 

-

class User(var name:String, var address:String){

    var id:Long

    var createdAt:String?

    var updatedAt:String?

}

 

 

-

Custom annotation 을 만든다.

@Qualifier

@Retention(RetentionPolicy.RUNTIME)

public @interface ActivityContext{

}

 

@Qualifier

@Retention(RetentionPolicy.RUNTIME)

public @interface ApplicationContext { 

}

 

@Qualifier

@Retention(RetentionPolicy.RUNTIME)

public @interface DatabaseInfo { 

}

 

@Scope

@Retention(RetentionPolicy.RUNTIME)

public @interface PerActivity { 

}

 

@Qualifier annotation 은 javax inject package 에 포함되어 있으며, 의존성을 한정하는 데 사용한다.

예를 들어 class 가 Application context 와 Activity context 를 둘 다 요청할 수 있다.

그러나 둘 다 context type 이다.

그래서 dagger2 에서는 무엇이 제공되어야 하는지 명시적으로 한정하기 위해 @qualifier 를 사용한다.

 

결론적으로 @Qualifier 는 2개의 same type 이지만 다른 instance 를 구분하기 위해 사용한다.

DatabaseInfo 는 class dependency 에서 database name 을 제공하기 위해 사용된다.

String class 가 dependency 로 제공되기 떄문에 이런 경우는 항상 qualifier 를 사용하여 이 모호한 문제를 해결하는 것이 좋다.

 

@Qualifier 대신 dagger2 에서는 @Named 를 사용할 수도 있다.

@Named 자체가 @Qualifier 로 annotate 되어 있다.

@Named 에 string identifier 를 제공할 수 있으며, 비슷한 class object 들이 이 string identifier 에 매핑될 수 있다.

 

@Scope 는 dependency object 가 유지되어야 하는 scope 를 한정한다.

 

 

-

@Singleton

@Inject

class DbHelper(@ApplicationContext context:Context, @DatabaseInfo dbName:String, @DatabaseInfo version:Integer) : SQLiteOpenHelper(context, dbName, null, version){

    ...

}

@Singleton 은 DbHelper 가 globally single instance 를 보장해준다.

constructor 에 @Inject 는 class 가 생성될 때 모든 param 에 대해 inject 하는 것을 명령한다.

@ApplicationContext qualifier 는 DbHelepr 가 dependency graph 에서 application context 를 받도록 한다.

@DatabaseInfo qualifier 는 dagger 에게 String 과 Integer 를 구분하게 한다.

 

 

-

@Singleton

@Inject

class SharedPrefsHelper(private val sharedPreferences:SharedPreferences){

    ...

}

 

 

-

@Singleton

@Inject

class DataManager(@ApplicationContext private val context:Context,

    dbHelper:DbHelper,

    sharedPrefsHelper:SharedPrefsHelper){

    ...

}

 

 

-

class DemoApplication : Application{

    protected lateinit var applicationComponent:ApplicationComponent

 

    @Inject

    val dataManager:DataManager

 

    companion object{

        fun get(context:Context):DemoApplication{

            return context.getApplicationContext() as DemoApplication

        }

    }

 

    override fun onCreate(){

        super.onCreate()

        applicationComponent = DaggerApplicationComponent.builder()

    .applicationModule(ApplicationModule(this))

    .build()

        applicationComponent.inject(this)

    }

 

    fun getComponent():ApplicationComponent{

        return applicationComponent

    }

}

 

 

-

class MainActivity : AppCompatActivity{

    @Inject

    val mDataManager:DataManager

 

    private val activityComponent by lazy { 

        DaggerActivityComponent.builder()

            .activityModule(ActivityModule(this))

            .applicationComponent(DemoApplication.get(this).getComponent())

            .build()

    }

 

    override protected fun onCreate(savedInstanceState:Bundle?){

        super.onCreate(savedInstanceState)

        setContentView(…)

 

        getActivityComponent().inject(this)

    }

 

    ...

}

 

 

-

@Module

class ApplicationModule(private val app:Application){

    @Provides

    @ApplicationContext

    fun provideContext():Context{

        return app

    }

 

    @Provides

    fun provideApplication();Application{

        return app

    }

 

    @Provides

    @DatabaseInfo

    fun provideDatabaseName():String{

        return “demo-dagger.db"

    }

 

    @Provides

    @DatabaseInfo

    fun provideDatabaseVersion():Int{

        return 2

    }

 

    @Provides

    fun provideSharedPrefs():SharedPreferences{

        return app.getSharedPreferences(“demo-prefs”, Context.MODE_PRIVATE)

    }

}

 

 

-

@Singleton

@Component(modules = ApplicationModule::class.java)

interface ApplicationComponent{

    fun inject(demoApp:DemoApplication)

 

    @ApplicationContext

    fun getContext():Context

 

    fun getApplication():Application

 

    fun getDataManager():DataManager

 

    fun getPreferenceHelper():SharedPrefsHelper

 

    fun getDbHelper():DbHelper

}

 

inject method 는 무엇인가?

field injection 을 할 때 (member variable 에 @Inject annotate) 우리는 dagger 에게 전달되는 class 를 scan 하도록 해야 한다.

 

 

-

@Module

class ActivityModule(private val activity:Activity){

    @Provides

    @ActivityContext

    fun provideContext():Context{

        return activity

    }

 

    @Provides

    fun provideActivity():Activity{

        return mActivity

    }

}

 

@PerActivity

@Component(dependencies = ApplicationComponent::class.java, modules = ActivityModule::class.java)

interface ActivityComponent{

    fun inject(activity:Activity)

}

 

ActivityComponent 에는 ApplicationComponent graph 가 추가된 것이다.

@PerActivity 는 activity 마다의 scope 를 정의한 것이다.

 

 

-

applicationComponent = DaggerApplicationComponent .builder()

    .applicationModule(ApplicationModule(this))

    build() 

applicationComponent.inject(this)

 

activityComponent = DaggerActivityComponent.builder()

    .activityModule(ActivityModule(this))

    .applicationComponent(DemoApplication.get(this).getComponent())

    .build()

activityComponent.inject(this)

 

 

-

@ApplicationContext, @ActivityContext 는 @Named(“application_context”), @Named(“activity_context”) 로 바꿀 수 있다.

그러나 @Named 보다는 @ApplicationContext 같은 구현이 더 좋은 것 같다.

 

 

-

참고 자료 : https://blog.mindorks.com/a-complete-guide-to-learn-dagger-2-b4c7a570d99c

 

더보기

@Named, @PerActivity, @scope, @Qualifier, annotation processor, automatic field inject, automatic field injection, constructor injection, custom annotation, Dagger1, dagger1 vs dagger2, dagger2, dependency connector, dependency consumer, dependency injection, Dependency Injection in Android, dependency producer, dependency 한정, field injection, gradle setting, inject method, injection 순서, inversion of control, Method Injection, private field inject, Reflection, [android] Dagger2 tutorial part2, 의존 한정

 

 

반응형

댓글