개발자이야기
mvvm + dagger 본문
mvvm + dagger
(AAC) Android Architecture Components
AAC ViewModel 과 LiveData 그리고 DataBinding을 사용했습니다.
우선 글을 시작하기에 앞서 AAC ViewModel은 mvvm의 vm을 말하는것은 아닙니다. AAC ViewModel 없이도 충분히 mvvm 디자인패턴 설계를 할 수 있습니다.
AAC의 ViewModel은 프래그먼트 간의 데이터 공유, 수명주기 관리를 편하게 해주는 라이브러리 입니다.
자세한 내용은 https://developer.android.com/topic/libraries/architecture/viewmodel?hl=ko
이번엔 간단한 예제를 이용한 MVVM 디자인 패턴과 Dagger 를 알아볼까 합니다.
Dagger
안드로이드에 Dagger를 사용하려면 안드로이드의 다음 특성에 대해 먼저 이해해야 한다. 첫째, 안드로이드는 하나의 애플리케이션 내에서 액티비티 또는 서비스 같은 생명 주기를 갖는 컴포넌트로 구성된다. 둘째, 프래그먼트는 단독으로 존재할 수 없으며, 반드시 액티비티 내에 존재한다. 셋째, 애플리케이션을 포함한 액티비티 또는 서비스와 같은 컴포넌트는 시스템에 의해서만 인스턴스화 된다.
애플리케이션의 생명 주기 동안 다양한 액티비티 및 서비스가 생성과 소멸을 반복할 수 있고, 하나의 액티비티 내에서는 마찬가지로 여러 프래그먼트가 생성과 소멸을 반복할 수 있다. 가장 큰 범위인 애플리케이션에서 일어나는 일들이므로 애플리케이션 생명 주기와 Dagger 컴포넌트의 생명 주기를 같이하는 애플리케이션 컴포넌트를 만든다. 액티비티 또는 서비스를 위한 Dagger 컴포넌트는 애플리케이션 컴포넌트의 서브 컴포넌트로 구성하고, 프래그먼트는 액티비티(서브) 컴포넌트의 서브 컴포넌트로 다시 지정한다.
위의 설명을 읽어봅시다. 간단히 Application은 최상위 Activity는 Appication의 서브로 계층적으로 Component를 구성해야합니다.
더 자세한 설명은 추후에 Dagger를 포스팅 할 예정이므로 이번 포스팅에선 코드를 보겠습니다.
ApplicationComponent
@Singleton
@Component(
modules = [
ActivityBindingModule::class, //1. activity 연결
FactoryModule::class, //2. 뷰모델 팩토리
AndroidSupportInjectionModule::class //3. 필수
]
)
interface ApplicationComponent : AndroidInjector<MainApplication> { //4. 필수
@Component.Factory
interface Factory {
fun create(@BindsInstance application: Application): ApplicationComponent
}
}
ApplicationComponent 부터 살펴보겠습니다.
1. ActivityBindingModule
@Module
abstract class ActivityBindingModule {
@ActivityScope
@ContributesAndroidInjector(modules = [
MainActivityModule::class
])
abstract fun mainActivity(): MainActivity
}
모듈로 넘어와 봅시다. ActivityBindingModule 을 ApplicationComponent와 연결해 줌으로써 서로 관계가 맺어졌습니다.
편하게 뎁스로 말해보자면 Application 은 1뎁스 이고 Activity는 2뎁스 입니다.
대거 구조를 설계할때 계층적 설계를 해야한다고 위에서 설명했습니다. 만약 MainActivity에 Fragment가 추가된다면
@Module
abstract class ActivityBindingModule {
@ActivityScope
@ContributesAndroidInjector(modules = [
MainActivityModule::class,
MainFragmentModule::class //추가
])
abstract fun mainActivity(): MainActivity
}
이와같이 fragment의 MainFragmentModule 을 연결해 줌으로써 계층적인 설계를 할 수 있습니다.
@Module
abstract class MainFragmentModule {
@ContributesAndroidInjector
abstract fun fragmentFirst(): FragmentFirst
@ContributesAndroidInjector(modules = [FragmentModule::class])
abstract fun fragmentSecond(): FragmentSecond
}
이런식으로 말이죠. 여기 있는 Fragment 들은 3뎁스가 되겠군요. 당연한 이야기 이겠지만 하위 뎁스는 상위 뎁스를 알지 못합니다. Fragment 뎁스인 3뎁스에서 특정 객체를 생성하여 주입을 준비해도 2뎁스인 Activity에서는 주입받을 수 없습니다. 위와 같은 dagger에서 이야기 하는 그래프 컨셉을 생각하여 설계한다면 좀 더 수월한 작업이 될거라 생각합니다.
설명이 너무 길어져 맨위에 있던 ApplicationComponent로 다시 돌아가 보겠습니다.
@Singleton
@Component(
modules = [
ActivityBindingModule::class, //1. activity 연결
FactoryModule::class, //2. 뷰모델 팩토리
AndroidSupportInjectionModule::class //3. 필수
]
)
interface ApplicationComponent : AndroidInjector<MainApplication> { //4. 필수
@Component.Factory
interface Factory {
fun create(@BindsInstance application: Application): ApplicationComponent
}
}
2. FactoryModule
@Module
abstract class FactoryModule {
@Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
현재 기반으로 하고 있는 예제는 aac ViewModel을 사용했습니다. ViewModel 에서 Repository를 인자로 받기 위해 추가한 factory 모듈입니다. FactoryModule은 application 뎁스에 추가한 모듈입니다. 앞으로 어떤 하위뎁스의 activity건 fragment건 ViewModelFactory를 주입받을 수 있습니다.
(단순 설명일뿐 어떤 뎁스에 모듈을 추가하는건 개발자의 선택입니다. 참고용으로 봐주시기 바랍니다.)
3. AndroidSupportInjectionModule,
4. AndroidInjector<MainApplication>
Dagger에는 사용방법이 몇가지가 있습니다. 위의 두가지(3, 4)는 제가 사용한 방법에서 손쉽게 Application 과 Activity들의 연결을 도와주는 역할을 하는 인터페이스와 클래스입니다.
class MainApplication : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerApplicationComponent.factory().create(this)
}
}
MainApplication 클래스를 한번 확인해 봅시다.
applicationInjector() 메소드의 리턴 타입이 AndroidInjector 인것을 확인 할 수 있습니다. 해당 메소드는 상속받은 추상화 클래스인 DaggerApplication 내에 존재하는 추상화 메소드 입니다.
이렇게 위의 ApplicationComponent 4번 AndroidInjector<MainApplication>와
MainApplication 에 있는 applicationInjector(): AndroidInject 메소드의 리턴타입의 관계가 성립됩니다. 이렇게 MainApplication 과 ApplicationComponent 연결이 됐습니다.
다시 한번 말씀드리면 DaggerApplication 을 사용하지 않고 사용할 수 있는 방법도 있습니다.
여기까지 Dagger 설명을 마치고 mvvm 디자인 패턴으로 넘어가봅시다. Dagger의 더 자세한 설명은 추후 포스팅 해보려 합니다.
MVVM
Repository 디자인 패턴을 간단히 넣은 예제이며 Repository 의 목적은 webservice, database 의 분리 입니다.
해당 예제에서는 webservice로 Retrofit통신을 사용했습니다.
Android Architecture
ViewModel, LiveData, DataBinding
MainActivity
abstract class BaseActivity<D: ViewDataBinding> : DaggerAppCompatActivity() {
protected lateinit var binding: D
protected abstract val layout: Int
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, layout)
binding.lifecycleOwner = this
}
}
class MainActivity : BaseActivity<ActivityMainBinding>() {
@Inject lateinit var factory: ViewModelProvider.Factory //1. ViewModelFactory
private val viewModel by viewModels<MainViewModel> {factory} //2.ViewModel
override val layout: Int
get() = R.layout.activity_main //3. layout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.apply {
viewModel = this@MainActivity.viewModel //4. databindg ViewModel
instagramRv.adapter = UserAdapter(this@MainActivity.viewModel) //5. recyclerview
}
viewModel.data.observe(this, Observer {
(binding.instagramRv.adapter as UserAdapter).setData(it) //5. recyclerview
})
}
}
MainActivity를 먼저 살펴보겠습니다.
1. ViewModelFactory
이전 Dagger에서 보셨던 FactoryModule 에서 생성된 factory 객체를 주입하여 사용합니다. ViewModelFactory는 ViewModel에 전달되는 인자가 필요할때 사용합니다.
2. ViewModel
viewModel = ViewModelProvider(this, factory).get(MainViewModel::class.java)
viewModels() 메소드는 위와같이 생성 하는것과 동일한 역할을 수행합니다.
3. layout
추상 클래스인 BaseActivity에 선언되어 있는 추상 멤버 변수입니다.
DataBinding을 BaseActivity 에서 공통적으로 생성해주기 위해 override하여 선언 해 주었습니다.
4. dataBinding ViewModel
dataBinding을 사용하기 위해 ViewModel을 셋팅해 주었습니다. 이제 xml 파일에서 ViewModel의 LiveData를 감시할 수 있게 되었습니다.
5. recyclerview
recyclerview의 셋팅과 retrofit 통신으로 이벤트를 받아 data를 셋팅해주는 부분입니다.
recyclerview는 아래와 같은 ui를 만들어줍니다.
MainViewModel
class MainViewModel @Inject constructor(private val repository: MainRepository) : ViewModel() {
private var _data: LiveData<List<Any>> = repository.getApi()
val data: LiveData<List<Any>>
get() = _data
fun clickItem(position: Int, itemList: List<Any>) {
repository.clickItem(position, itemList)
}
override fun onCleared() {
repository.onCleared()
super.onCleared()
}
}
MainViewModel 클래스에서 @Inject constructor 부분은 생성자 주입을 대거에게 알려주는 것이며 MainRepository와 연결되게 됩니다. 자세한 설명은 추후 Dagger 사용법에서 자세히 설명해보겠습니다.
멤버변수 _data: LiveData 는 repository.getApi() 를 호출함으로써 api를 호출하여 데이터 발행까지 해주고 있습니다.
해당 LiveData의 이벤트를 받아 데이터를 가지고 recyclerview 데이터 셋팅을 해주게 됩니다.
MainRepository
class MainRepository @Inject constructor() {
private val disposable: CompositeDisposable = CompositeDisposable()
var data: MutableLiveData<List<Any>> = MutableLiveData()
fun getApi(): LiveData<List<Any>> {
disposable.add(
ApiConnection.instance()
.apiService.getAPI()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
it.setItemIndex()
data.value = it.contacts
}, {
//error
})
)
return data
}
fun onCleared() {
disposable.dispose()
}
}
Repository의 역할은 Webservice를 담당하게 됩니다. 각종 통신 라이브러리, 데이터베이스 등 Repository에서 비즈니스 로직을 처리하게 됩니다.
위의 onCleared() 메소드는 ViewModel에서 생명주기가 다했을대 호출되며 해당 메소드 내에 RxJava 의 observable을 해제해 주고 있습니다.
설명에 미흡한 부분이 많습니다. 추가적으로 보완할 예정이며 언제든 틀린 내용이나 피드백은 환영합니다.
읽어주셔서 감사합니다.
예제 소스
'android' 카테고리의 다른 글
android 수명주기 (0) | 2021.07.08 |
---|---|
android Dagger (0) | 2021.06.09 |
android AAC LiveData - switchMap, map (0) | 2021.05.28 |
dagger @binds 어노테이션 (1) | 2021.05.23 |
의존성주입(DI) android dagger (0) | 2021.05.22 |