일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 베드테이블
- 브런치작가되기
- 리얼하다
- 한달독서
- 북한살둘레길
- 목적중심리더십
- 자취필수템
- 재택근무
- 면접
- 베드트레이
- 테트리스
- T자형인재
- 어떻게 나답게 살 것인가
- 커스텀린트
- 프래그먼트
- 슬기로운 온라인 게임
- 목적 중심 리더십
- 아비투스
- 1일1커밋
- 함수형 프로그래밍
- 한달어스
- 끝말잇기
- 한단어의힘
- 좌식테이블
- 안드로이드
- 캐치마인드
- 소프시스
- 지지않는다는말
- 한달브런치북만들기
- 소프시스 밤부 좌식 엑슬 테이블
- Today
- Total
정상에서 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 을 사용하여 의존성을 없앴습니다. 내용이 궁금하신 분들은 제가 정리한 아래 링크를 확인해 보세요.
- Android Architecture Component(AAC)
<작업>
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 값을 동기화 시킵니다.
<참고자료>
'안드로이드' 카테고리의 다른 글
[리액티브 프로그래밍] 스케줄러 개념 배우기 (0) | 2019.01.03 |
---|---|
[Android,Material with refresh] 머테리얼 변형 예제 (0) | 2019.01.02 |
[리액티브 프로그래밍] 리액티브 연산자의 활용 (0) | 2018.12.19 |
[리액티브 프로그래밍] 리액티브 연산자 입문 (0) | 2018.12.19 |
[리액티브 프로그래밍] 리액티브 프로그래밍 이란? (0) | 2018.12.18 |