정상에서 IT를 외치다

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

안드로이드

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

사용자 Black-Jin 2018. 12. 27. 16:56
728x90
반응형


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


안드로이드 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 패턴

반응형
3 Comments
  • 프로필사진 남갯 2019.04.15 09:42 신고 MVVM도 하셨군요 대단하시네요
  • 프로필사진 창이 2020.01.09 17:59 안녕하세요. 예제를 참고하면서 공부중에 있습니다. Dagger가 너무 어려워서 Koin부터 해보려고 하는데 Koin으로 MVVM을 구성할시 Viewmodel에서 api를 여러개 사용하고 싶으면 매개변수로 여러개 만들어야 하나요? di를 안하고 했을때는 Viewmodel에서 repository를 여러개 생성해서 사용할 수 있는데 di를 쓰려면 미리 구조가 정해져서 사용해야 되는거 같더라구요
  • 프로필사진 사용자 Black-Jin 2020.01.13 13:27 신고 안녕하세요. 창이님
    댓글에 적어주신 것처럼 Viewmodel에서 매개변수를 여러개 만들어서 사용하시면 됩니다. 이때는 by inject()가 아닌 by viewmodel()을 사용해 주시면 라이프사이클에 맞춰 koin이 주입을 해줍니다. 자세한 사용법은 아래 링크를 참고하시면 될것 같습니다.

    https://start.insert-koin.io/#/quickstart/android-viewmodel

    di를 쓰면 미리 구조를 정해 사용하는게 맞습니다. dagger 같은 경우에는 componet 와 그 아래 subcomponet 를 통해 그래프와 같은 구조로 더 세밀하게 작업을 할 수가 있습니다.
댓글쓰기 폼