MVVM과 Android DataBinding
MVVM에서의 가장 큰 특징은 옵저빙 이다.
- 리액티브 프로그래밍에서 말하는 옵저버 패턴과 같은 의미.
- View가 ViewModel이 가진 데이터를 옵저빙 하고 있다가 ViewModel의 데이터가 변경되면 자동으로 View도 갱신되는 방식
- 자동으로 View에 notify가 간다고 생각하면 된다.
- 그래서 ViewModel은 View에 대한 참조를 가지지 않는다. ViewModel입장에서는 내껏만 바꾸면 다른건 신경 안써도 되는거다.
- (그리고 이 옵저빙 과정에서 발생하는 View->ViewModel의 의존성을 줄이기 위해안드로이드 데이터 바인딩 을 사용한다. 즉, 데이터 바인딩과 MVVM은 별개의 개념이다.)
- 즉, MVVM으로 컴포넌트를 분리한다는건 기존처럼 Activity(View)에서 ViewModel의 함수를 호출하는 식으로 분리하는 것이 아니다.
- 물론 이런 식의 호출이 필요한 순간이 있기는 하다만, 이런식의 기존 분리 방식과는 전혀 다르다고 볼 수 있다.
- 리액티브에서 말하는 옵저버 패턴
databinding
- 데이터 바인딩은 MVVM과 별개의 개념으로 findViewById 같은 것 없이 바로 xml의 UI 구성요소와 java간의 데이터를 바인딩 할 수 있도록 해준다. 애시당초 그런 기능이다.
- 데이터 결합 라이브러리는 프로그래매틱 방식이 아니라 선언적 형식으로 레이아웃의 UI 구성요소를 앱의 데이터 소스와 결합 할 수 있는 지원 라이브러리입니다.
- data binding의 장점?
- 화면 데이터 상태와 세션 데이터 상태가 일치하도록 만들 수 있다.
- MVVM에서 왜 databinding이 필요한지를 보여주는 예제. 코드가 많이 생략되어 있으니 컨셉만 이해하도록.
- 단순히 값을 대입하는 작업이 아니라 좀 더 복잡한 작업, 이를테면 어떤 작업의 성공 또는 실패 시 Toast를 띄운다던가 하는 등의 뷰 작업은 Activity에서 .observe() {}를 작성해주어서 데이터 변경 시 트리거 되는 {} 에서 실행하도록 해야 한다.
예제
- 좋은 예제. 기초부터 RecyclerView에 적용하는 것 까지 다 나와있다.
- 안드로이드 공식 좋은 앱 아키텍쳐와 관심 분리 대한 설명
- 안드로이드 공식 AAC에 대한 설명
- 안드로이드 공식 ViewModel에 대한 설명
- codelabs android-lifecycles
각 예제가 모두 뷰모델을 어떻게 만들고, 어떤 로직을 액티비티에서 처리하는지 뷰모델에서 처리하는지 뭐 이런 것들이 판이하게 다르다.
서치하다보면 이상한 예제도 너무 많다… 이게 best practice가 아닌 것 같은데?? 싶은 예제들도 있고, 애초에 MVVM을 쓰는데 VM에서 V로의 의존성이 강하게 존재하는 코드들도 있고… 이러면 MVVM을 쓰는 의미가 없지 않나 싶은 예제들이…
아무튼,
제일 확실한건 안드로이드의 공식 예제임. 그걸 따라가도록 한다.
- MVVM으로 짤 때 하지 말아야 할 것과 좋은 컨벤션들
- 안드로이드의 공식 예제. databinding을 이용해서 MVVM 패턴으로 짜려면 실제로 어떻게 짜야하지?는 이걸 보면 된다.
- 안드로이드의 또 다른 MVVM 예제 코드
- 모든 사항(MVVM, databinding, 그 밖의 library)을 고려한 예제 코드
- ViewModel은 반드시 ViewModelProvider를 통해서 생성하도록 한다.
- 왜 반드시 ViewModelProvider를 거쳐야 하느냐?는 이걸 읽어보면 된다.
- ViewModel에서 Intent를 써야 할 때? 여기서 startActivity를 불러도 되는 것인가?
- how-to-get-context-in-android-mvvm-viewmodel/51451877
- [번역] DroidKaigi 2017 ~ DataBinding 로 구현하는 MVVM Architecture : 어떤 상황에서는 어떻게 처리해야 하는지에 대한 다양한 케이스가 있음.
- [번역] DroidKaigi 2018 ~ MVVM Best Practicee
데이터 바인딩 (databinding) 사용법
databinding 의존성 추가하기 구글 docs 아래 설정이 끝이다.
1
2
3
4
5
6
7
8
apply plugin: 'kotlin-kapt'
android {
...
dataBinding {
enabled = true
}
}
kapt는 annotationProcessing을 대체하는 코틀린 애너테이션 Processor
[TED]데이터 바인딩의 기초부터 예제까지. @BindingAdapter와 @BindingConversion. MVVM과는 전혀 관련이 없으니 주의.
1
2
3
4
5
6
7
<data>
<variable
name="vm"
type="com.tistory.umbum.github_issue_widget_app.viewmodel.RepoSelectViewModel" />
</data>
<android.support.v7.widget.RecyclerView
android:id="@+id/repo_list_view" />
1
2
3
다음과 같이 접근 가능하다
binding.vm // type은 <RepoSelectViewModel>
binding.repoListView // type은 <RecyclerView>
Model -> ViewModel -> View로의 데이터 파급
1
2
3
4
5
6
class UserProfileViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
userRepository: UserRepository) : ViewModel() {
val userId : String = savedStateHandle["uid"] ?: throw IllegalArgumentException("missing user id")
val user : LiveData<User> = userRepository.getUser(userId)
}
여기서 userRepository.getUser(userId)가 LiveData
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class UserRepository {
private val webservice: Webservice = TODO()
// ...
fun getUser(userId: String): LiveData<User> {
// This isn't an optimal implementation. We'll fix it later.
val data = MutableLiveData<User>()
webservice.getUser(userId).enqueue(object : Callback<User> {
override fun onResponse(call: Call<User>, response: Response<User>) {
data.value = response.body()
}
// Error case is left out for brevity.
override fun onFailure(call: Call<User>, t: Throwable) {
TODO()
}
})
return data
}
}
Model은 자기 자신에 대한 데이터를 유지하고 있는게 맞나?
1
2
3
4
5
6
7
View -------------> ViewModel <------------ Repository
(field를 observe) (Model을 return
field에 할당)
1. LiveData를 리턴?
== 액티비티가 부셔지면 같이 없어져야 하는 경우.
2. Observable을 리턴?
== Rx를 사용할 때.
또는 Repository 없이 바로 ViewModel에서 Model을 생성해도 무관하다.
Repository의 역할은 여러 데이터 소스로부터 Model을 가져오는 것을 추상화하는 역할 이므로, 단일 데이터 소스로부터 가져와서 별다른 추상화가 필요하지 않다면 ViewModel에서 바로 retrofit같은걸 써서 가져와도 괜찮음.