정상에서 IT를 외치다

[Android, MVVM] ViewModel, LiveData, DataBinding, Koin 을 사용한 MVVM 본문

안드로이드

[Android, MVVM] ViewModel, LiveData, DataBinding, Koin 을 사용한 MVVM

Black-Jin 2018. 12. 27. 16:56
반응형


안녕하세요. 블랙진입니다.


안드로이드 MVVM 패턴에 대한 토이프로젝트 리뷰 포스팅입니다. AAC의 ViewModel 과 LiveData 를 사용했으며 DataBinding 을 사용하여 MVVM 패턴을 구현하였고 Koin 을 사용하여 의존성을 없앴습니다. 내용이 궁금하신 분들은 제가 정리한 아래 링크를 확인해 보세요.


데이터바인딩 라이브러리 사용기

MVVM 디자인 패턴의 기본 이해

Android Architecture Component(AAC)

Koin을 사용한 의존성 주입 기본 예제


<작업>


1. http://api.randomuser.me/?results=10 API를 통해 랜덤한 깃허브 유저 데이터를 받아옵니다.

2. 받아온 데이터를 Recycler View 에 보여줍니다.

3. API 데이터 외 Like 값을 넣을 수 있는 로컬 변수를 추가해 주었습니다.

4. 유저의 상세화면에서 Like 버튼을 누른 갯수 만큼 유저 리스트에서 유저의 Like 값이 증가됩니다.


위 작업물은 링크에서 다운받으실 수 있습니다.


그럼 <작업> 에서 보이는 앱을 MVVM 패턴으로 바꾼 코드에 대해 리뷰를 시작하겠습니다.

MVVM 변환 작업이 끝난 결과물을 보고 싶은신 분인 MyAndroidArchitecture를 확인해 주세요.



유저 목록이 보이는 MainActivity


1. Koin 을 사용하여 api 를 의존성 주입 합니다.

val apiModule = module {

//GithubAPi
single {

Retrofit.Builder()
.baseUrl(BASE_URL)
.client(get())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
.create(GithubApi::class.java)

}

// OkHttpClient
single {

// 네트뭐크 통신에 사용할 클라이언트 객체를 생성합니다.
// 이 클라이언트를 통해 오고 가는 네트워크 요청/응답을 로그로 표시하도록 합니다.
OkHttpClient.Builder().addInterceptor(get() as HttpLoggingInterceptor).build()

}

// HttpLoggingInterceptor
single {

// 네트워크 요청/응답을 로그에 표시하는 Interceptor 객체를 생성합니다.
HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}

}

}

Java 에서는 의존성 주입으로 Dagger2 를 많이 사용합니다. 물론 코틀린에서도 이를 사용할 수 있지만 Dagger2 는 진입장벽이 매우 높습니다. 이에 Koin은 코틀린 개발자를 위한 가벼운 의존성 주입 프레임워크입니다. 위와 같이 single 함수를 사용해 실글톤 객체를 생성하실 수 있습니다.

class MyApplication : Application() {

override fun onCreate() {
super.onCreate()

startKoin(this, listOf(apiModule))

}
}

이렇게 모듈을 생성한 후에는 MyApplication 에서 startKoin을 통해 활성화 시켜주면 모든 설정은 끝납니다.


이렇게 MainActivity 에서 api 변수를 의존성 주입을 통해 불러왔습니다.

private val api: GithubApi by inject()


2. MainActivity 에서의 AAC

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
title = "RANDOM USER"

// 뷰 모델 팩토리 선언
userListViewModelFactory = UserListViewModelFactory(adapter, api)

// 뷰 모델 초기화 및 관찰자 연결
userListViewModel = ViewModelProviders.of(this, userListViewModelFactory)
.get(UserListViewModel::class.java)

// 데이터 바인딩에 뷰 모델 연결
viewDataBinding.model = userListViewModel

// 데이터 바인딩에 LifecycleOwner 연결하여 liveData 를 DataBinding 과 사용 할 수 있게 함
viewDataBinding.setLifecycleOwner(this)

}

설명은 주석을 참고해 주세요.



3. activity_main

<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
bind_adapter="@{model.adapter}"
bind_items="@{model.items}"
/>

recyclerView 만 살펴보겠습니다.

@BindingAdapter("bind_adapter")
fun setBindAdapter(view: RecyclerView, adapter: MainAdapter?) {
adapter?.let {
view.adapter = it
}
}

@BindingAdapter("bind_items")
fun setBindItems(view : RecyclerView, items : List<User>?) {
items?.let {

val adapter = view.adapter as MainAdapter
adapter.setItems(items)
adapter.notifyDataSetChanged()

}
}

BindingAdapter를 사용해 adapter 를 연결 시키고 item 을 setting 했습니다. 이러한 방법을 통해 Activity 가 아닌 XML 에서 RecyclerView 작업을 모두 처리할 수 있습니다.



4. UserListViewModel


AAC의 ViewModel 을 상속하고 있는 ViewModel 입니다.

LiveData 를 통해 데이터를 라이프 사이클에 맞춰 동기화 시킬 수 있습니다. 또한 ViewModelProvider.Factory 를 통해 VIewModel 을 선언할 때 adapter 와 api 변수를 넘겨 사용할 수 있습니다.

class UserListViewModelFactory(private val mainAdapter: MainAdapter, private val api: GithubApi)
: ViewModelProvider.Factory {

override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return UserListViewModel(mainAdapter, api) as T
}

}

이를 통해 Activity가 아닌 ViewModel 에서 통신작업을 모두 처리할 수 있게 됩니다. 이는 Acitivty와의 의존성을 없애는 코드가 됩니다.

private val _progressView   = MutableLiveData<Int>()
private val _items = MutableLiveData<List<User>>()
private val _adapter = MutableLiveData<MainAdapter>().apply { value = mainAdapter }

//mutableLiveData 를 immutable 하게 노출
//ViewModel 내부에서는 Mutable 한 데이터를 외부에서는 Immutable 하게 사용하도록 제약을 주기 위함
val progressView: LiveData<Int> get() = _progressView
val items: LiveData<List<User>> get() = _items
val adapter: LiveData<MainAdapter> get() = _adapter

같은 내용의 변수를 MutableLiveData 와 LiveData 2개로 나눴습니다. LiveData 프로퍼티를 위와 같이 노출 하는 것은 ViewModel 내부에서만 변경 가능하게 제약을 주기 위해서 입니다. 이렇게 하면 View 에서 ViewModel 의 데이터상태를 변경하지 못하게 됩니다. 이는 아래 참고자료에서 발췌한 부분으로 코틀릴 공식 다큐멘트에 있는 내용입니다.


fun registerEvent() {

addDisposable(
RxEvent.getInstance()
.observable
.subscribe { user ->
if (user is User) {
_adapter.value?.updateView(user)
}
}
)
}

UserListViewModel 에 있는 registerEvent 는 Rx를 사용한 이벤트 버스 입니다. 위 함수를 통해 유저 상세화면에서 누를 Like 버튼의 값들이 실시간으로 유저리스트에 반영됩니다.




유저 상세 내용과 Like 할 수 있는 DetailActivity


1. intent 을 통해 user 정보를 받아 옵니다.

private fun getUserFromIntent() {

// intent data 받기
user = intent.getSerializableExtra(KEY_USER) as User

// app bar title 설정
title = user.fullName

// viewMode 설정
setData()
}

private fun setData() {

userViewModel.setUser(user)

}

intent 를 통해 User 정보를 받아 온 후 ViewModel에 설정합니다.



2. DetailActivity 에서의 AAC

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// 뷰 모델 초기화 및 관찰자 연결
userViewModel = ViewModelProviders.of(this).get(UserViewModel::class.java)

// 데이터 바인딩에 뷰 모델 연결
viewDataBinding.user = userViewModel

// 데이터 바인딩에 LifecycleOwner 연결하여 liveData 를 DataBinding 과 사용 할 수 있게 함
viewDataBinding.setLifecycleOwner(this)

// 데이터 초기화
getUserFromIntent()
}

설명은 주석을 참고해 주세요.



3. UserViewModel

// plus like count and event to rxBus when click like button
fun addLikeCnt() {

_likeCnt.value = _likeCnt.value?.plus(1)

this.user?.likeCnt = _likeCnt.value ?: 0

RxEvent.getInstance().sendEvent(user)
}

ViewModel 에서의 addLikeCnt 함수를 통해 Like 값을 plus 해줄 수 있습니다. 또한 이 이벤트를 RxEvent를 통해 유저 리스트의 like 값을 동기화 시킵니다.



<참고자료>


한옥 사는 개발자 - Android MVVM 패턴

반응형
Comments