Android 공부

감자튀김의 다시 쓰는 Android MVVM(1) - DI


-1. MVVM 메인 가이드:


https://developer.android.com/jetpack/docs/guide


구글의 앱 아키텍처 가이드 개념을 기본으로 해서 프로젝트를 관리해야 한다고 생각합니다. MVVM을 사용하기 이전에 해당 자료를 통해서 왜 MVVM을 사용해야 하는지에 대한 대답을 얻을 수 있을 것이라고 생각합니다. docs에서는 이와 같은 이유로 MVVM을 사용한다고 합니다.


- 안드로이드 앱은 액티비티, 서비스, 브로드캐스트, 콘텐트 프로바이더, 프레그먼트 등 다양한 앱 구성요소로 되어있습니다. 특정 앱을 사용하다가 다른 앱으로 넘어가는 경우와 같이 사용환경이 중단될 수 있는데, 이럴 경우에 흐름을 올바르게 제어해야합니다. 그렇기 때문에 운영체제는 앱구성요소를 언제든지 제거할 수 있다는 전제하에 구조를 만들어야 하며, 앱 구성요소에 앱 데이터나 상태를 저장해서는 안됩니다.


- Activity, Fragment는 소유의 대상이 아니라 안드로이드 OS와 앱 사이의 계약을 나타내는 단순 클래스에 불과합니다. 관심사 분리를 통해서 의존성을 제거시켜줘야 합니다. 이를 통해서 생명주기에 관련된 다양한 문제들을 해결할 수 있다고 합니다.



0. 서론


안녕하세요. 부스트캠프를 통해서 조금이나마 실력업을 시키고 온 감자튀김입니다.

그래서 현재 이해하고 있는 MVVM을 정리하고, 글을 작성하려고 합니다. 


소스코드 : https://github.com/hakzzang/MVVM_Dagger_With_Upbit_API



DI를 이해하고 있는 선에서 내용을 정리하려고 합니다. 

틀린 내용이 많을 수도 있으니 조심해서 읽어주세요!


부스트캠프를 진행하면서, Dagger2를 사용할까... 하며 스터디를 했던 내용입니다.

해당 내용을 통해서 프로젝트에서 Dagger2를 사용해본 경험을 얻었습니다!


https://github.com/hakzzang/DAGGER_MVVM_KOREA/blob/master/DAGGER_STUDY_1.md

https://github.com/hakzzang/DAGGER_MVVM_KOREA/blob/master/DAGGER_STUDY_2.md


1. 목표


우선, 해당 프로젝트의 목표는 저의 공부와 함께, MVVM을 통해서 업비트 블록체인 조회 앱을 만들 것이고, 여기에는 Dagger2, MVVM를 중점적으로 사용할 거에요!! API를 통해서 UpbitAPI를 가져오는 Repository pattern을 학습해보고, MotionLayout을 통해서 움직이는 애니메이션을 만들어보겠습니다.


1.1. MVVM 사용 이유


- Guide에서 MVVM을 사용해야 하는 이유 : 관심사 분리를 통해서 생명주기에 생길 수 있는 다양한 문제점을 해결할 수 있다. 

- 사용을 하고 느낀 점 : MVVM을 사용한 Model의 변화를 감시하고, 그것에 따라서 ViewModel에 값이 셋팅되게 되면, View가 변하는게 매력적이었습니다.


2. DI


2.1. DI를 통해 MVVM에서 얻는 이점:


 레파지토리에서 데이터를 가져오기 위해선 WebService Class를 호출해야 합니다. 그렇기 위해서는 WebService의 종속성도 알아야 하는데, 이 때 레파지토리 별로 Service를 무분별하게 만들게 되면, 클래스가 무거워질 수 있어서 외부에서 의존성 주입을 통해 유일하게 WebService Class 유지시켜 주기 위해서 사용합니다.


2.2. DI

구조적이거나 기술적인 참조는 해당 블로그 글을 보고 공부하게 되었습니다.


https://www.charlezz.com/?p=428


프로젝트를 실제로 진행해보면서, 느꼈던 DI를 사용하는 이유는 이와 같았습니다.


- DI를 통해서 Repository 패턴을 사용하게 되면, 외부에서 해당 Repository를 Inject해서 객체를 주입해주고, DI가 객체를 관리해줬다.

- 사용해야하는 객체를 정확하게 파악하고, DI가 이러한 객체를 관리해주었다.

- Koin을 통해 DI를 사용할 땐, LifeCycle에 종속시켜 DI를 사용해서, 메모리의 이점이 있지 않을까 싶었다.


이와 같이, DI를 사용함에 있어 크게 와닿지 않는 이유들을 나열했지만, 한 번, DI를 통해서 프로젝트를 진행해보니, 해당 객체를 DI가 관리해주도록 코드를 작성하게 되고, Inject를 통해서 유연하게 객체를 주입해주며, 이를 통해서 무분별한 사용을 방지, 그리고 객체의 재활용성의 증진이 있었습니다.


apply plugin: 'kotlin-kapt'

dependencies {

    def daggerVer = "2.21"
    implementation "com.google.dagger:dagger-android:$daggerVer"
    implementation "com.google.dagger:dagger-android-support:$daggerVer" // if you use the support libraries
    kapt "com.google.dagger:dagger-android-processor:$daggerVer"
    kapt "com.google.dagger:dagger-compiler:$daggerVer"
}

DI의 version은 2.21이었고, 이와 같이 library를 사용해서 프로젝트를 진행했습니다.


저는 대거를 쉽게 이해하기 위해서, 대략적인 구조도를 그려보면서 공부를 했습니다. 하나씩, 따라가면서 그려보게 되면, 이해하기 쉽고, 추후 프로젝트에도 유연하게 반영할 수 있는 것이 장점이라고 생각합니다.


2.1. 구성도


2.1.0. Annotation


1
2
3
4
5
6
7
@Scope
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class ActivityScope
 
@Scope
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class ViewModelScope
cs

Annotation은 이와 같이 만들어지며, Dagger에서는 Scope를 탐지하기 위해서 만들어서 사용하고, 해당 프로젝트에서는 라이프사이클에 종속시키 목적으로 ActivityScope나 ViewModelScope를 라이프사이클에 종속시키기 위해서 Annotation을 만들었습니다.

Dagger에서는 각종 Annotation을 제공하고 있습니다.

@Inject : 대거를 통해서 새로운 class의 객체를 만들기 위해서 사용한다.

@Provide : 만들어진 @Inject의 객체의 의존성을 주입하기 위해서 사용한다.

@Singleton : 객체를 싱글톤으로 유지시키기 위해서 사용하는 변수입니다.

@Component : 에노테이션을 적용하여, 인터페이스와 modules의 변수들의 타입들을 전달한다.

@ContributesAndroidInjector : 추상화된 class의 의존성 객체를 만들기 위해서 사용한다.


2.2.1. BaseApplication.kt


1
2
3
4
5
class BaseApplication : DaggerApplication() {
    override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
        return DaggerApplicationComponent.builder().create(this)
    }
}
cs


BaseApplication은 Application class 용도로 Dagger를 사용하기 위해서 사용하는 것이고, applicationInjector()를 override해서 DaggerApplicationComponent를 만들어서 return 해줍니다. 여기서 DaggerApplicationComponent는 밑의 ApplicationComponent가 자동적으로 Dagger~라는 이름이 붙어서 만들어진 것입니다. 이것은 Dagger2에 의해서 interface가 Dagger라는 이름이 해당 만들어지는 관례입니다.


2.2.2. ApplicationComponent.kt


1
2
3
4
5
6
@Singleton
@Component(modules = [AndroidSupportInjectionModule::class, ApplicationModule::class, ActivityModule::class, ViewModelModule::class])
interface ApplicationComponent : AndroidInjector<BaseApplication> {
    @Component.Builder
    abstract class Builder : AndroidInjector.Builder<BaseApplication>()
}
cs

ApplicationComponent는 Singleton객체로 생성되고, @Component 에노테이션을 적용하여, 인터페이스와 modules의 변수들의 타입들을 전달합니다. 우리는 여기서 AndroidSupportInjectionModule, ApplicationoModule, ActivityModule, ViewModelModule의 변수들의 타입을 전달받습니다.


2.2.3. ApplicationModule


1
2
3
4
5
6
@Module
class ApplicationModule{
    @Provides
    @Singleton
    fun provideApplicationModule() = "EMPTY"
}
cs

ApplicationModule은 Application에서 사용하는 무엇인가의 객체를 주입해주기 위해서 만들었는데, 현재 사용하지 않습니다.

2.2.4. ActivityModule


1
2
3
4
5
6
@Module
abstract class ActivityModule{
    @ActivityScope
    @ContributesAndroidInjector(modules = [MainActivityModule::class])
    abstract fun getMainActivity() : MainActivity
}
cs


ActivityModule은 만들어지는 각종 Activity의 객체를 갖고 있는 Module입니다.


2.2.5. ViewModelModule


1
2
3
4
5
6
@Module
abstract class ViewModelModule{
    @ViewModelScope
    @ContributesAndroidInjector(modules = [MainViewModelModule::class])
    abstract fun getMainViewModel() : MainViewModel
}
cs

ViewModelModule은 ViewModel에 관련된 객체를 주입하기 위해서 만들어져있습니다.
이와 같이 집중적으로 Module들이 만들어져있고, 해당 객체를 중심으로 DI를 제공해주고 있습니다.

3. 느낀 점

Dagger2를 완벽하게 사용하기 위해서는 LifeCycle에 종속시켜야하는 로직이 필요하다고 생각하는데, 해당 내용에 대해서 조금 더 공부하고, 다음 블로그에 올리도록 하겠습니다. 또한 해당 코드들을 처음 사용하기엔 어렵지만, 추후에 프로젝트를 진행하면 각종 코드가 분리가 되고, 유지보수적인 측면에서 많은 이점을 얻을 수 있어서, 단점보단 장점이 더 많다고 생각합니다.