정승욱 (Steve)
8 yrs Android Engineer
GrabTaxi Geo Tech Leader
GDE Singapore
gdg slack : @seongug.jung (가입 요청 링크)
medium : @jsuch2362
MVVM 과 Grab 아키텍쳐
5월 드로이드나이츠
MVVM 에 대해 이해한다고 전제로 공유
내가 본 MVVM 예시
class MainViewModel {

val title: LiveData<String>

}
class MainActivity {

val viewModel: ViewModel

val textView: Textview

fun onCreated() {

viewModel.title.observe(this) {

textVew.text = it

}

}

}
내가 본 MVVM 예시
class MainViewModel {

val title: LiveData<String>

}
class MainActivity {

val viewModel: ViewModel

val textView: Textview

fun onCreated() {

viewModel.title.observe(this) {

textVew.text = it

}

}

}
문제점?
class MainViewModel {

val title: LiveData<String>

}
class MainActivity {

val viewModel: ViewModel

val textView: Textview

fun onCreated() {

viewModel.title.observe(this) {

textVew.text = it

}

}

}
문제가 된 이유
io17 Architecture Component 데뷔
구글의 예시와 쏟아지는 개발자 예시들
유사한 이름의 MVVM 에 주목
여러분들이 알아두셔야 할 것
MVVM 은 AAC 의 ViewModel 연관성이 없다
ViewModel 이라는 이름은 왜?
ACC 의 ViewModel
Activity/Fragment 의 Lifecycle 의존성 낮추는 것
LiveData 는 Repository 로부터 데이터 변화 반응/적용
ViewModel 은 LiveData 로부터 View 에 필요한 데이터를 관리
View 에 필요한 데이터를 관리하기 위함
ViewModel 그 자체의 의미는 맞으나
MVVM 은 다른 것
MVVM 에서 View - ViewModel 관계
View 와 ViewModel 의 연결을 최소화
ViewModel 은 데이터의 변화를 View 에 전달
View 는 화면 정보의 변화를 ViewModel 에 전달
View 는 XML, Activity/Fragment 가 아니다
MVVM

핵심은 Databinding
이렇게 되어야 합니다
class MainActivity {

val viewModel: ViewModel

val textView: Textview
fun onCreated() {

viewModel.title.observe(this) {

textVew.text = it

}

}

}
class MainActivity {

val viewModel: ViewModel

fun onCreated() {

dataBinding.setVariable(BR.vm, viewModel)

}

}
이렇게 되어야 합니다
class MainActivity {

val viewModel: ViewModel

fun onCreated() {

dataBinding.setVariable(BR.vm, viewModel)

}

}
MVVM 본격 적용하기
문제는 안드로이드
문제는 안드로이드
문제 사항들
1. Lifecycle
2. Databinding 으로 다 표현하기 힘든 View 이벤트
3. Resource 등 Context 를 접근해야 하는 경우
Lifecycle
RxLifecycle
open class RxActivity {

val rxbinder: RxBinder

fun onCreated() {

rxbinder.apply(ON_CREATED)

}

}
class ViewModel(val rxbinder: RxBinder) {

init {

rxbinder.bind(ON_DESTROYED) {

Observable....

}

}

}
class RxBinder {

val map: Map<Event, CompositDisposable>

fun apply(lifeEvent: Event) {

map[lifeEvent]?.clear()

}

fun bind(lifeEvent: Event, body: () -> Disposable) {

map[lifeEvent].add(body())

}

}
View 변화 감지
ViewUsecase
class GetVisibleAreaUsecase(view1) {

fun observe(): Observable<Rect> {

return Observable.create { e ->

e.onNext(view1.height)

view1.addGlobalLayoutChange {

e.onNext(view1.height)

}

}.distinctUntilChanged()

}

}
class ViewModel(usecase:GetVisibleAreaUsecae) {

init {

usecase.observe().subscribe {

/* do something*/

}.bindUntil(rxbinder)

}

}
Resource 접근
ResourceProvider
class ResourceProvider(context: Context) {

fun string(resId: String) = context.resource.getString(resId)

}
Activity Result
Activity Result
class Activity {

val resultManager

fun onActivityResult() {

resultManager.onResult()

}

}
class ResultManager {

fun listen(observer)

fun onResult()

}
class ViewModel(resultManager) {

init {

resultManager.listen {

// do something

}

}

}
모든 것이 구현되면...
대략...
class ViewModel(rxbinder, usecase, resultManager) {

init {

// many things are here

}

}
class Activity: ResultableRxActivity {

fun onCreated() {

dataBinding.setVariable(BR.vm, viewModel)

}

}
한단계 더 들어가보기
Activity 다시보기
Activity 다시보기
class MainActivity: RxResultableActivity {

@Inject lateinit var vm: MainViewModel

fun onCreated() {

dataBinding.setVariable(BR.vm, vm)

}

}
껍데기 뿐인 Activity
하는 역할은 DataBinding 과 DI
View 에 대해 다시 생각하기
단일 Layout
Layout 을 조각내보기
이 화면에 몇개의 뷰 조각이
있을까요?
1
5
6
7
2
3
4
각 Unit 을 Node 로 구분
Node 가 구성될 때 ViewModel 을 바인딩
Node 빌드 코드 - 1 : Activity
class ConfirmActivity {

fun onCreated() {

BackButtonNode({parentView}).dependency(root).build()

TaxiTypeNode({parentView}).dependency(root).build()

ExtraInfoNode({parentView}.dependency(root).build()

PayConfirmNode({parentView}).dependency(root).build()

// ..기타 등등....

}

}
Node 빌드 코드 - 2 : TaxiTypeNode
class TaxiTypeNode(parentView:() -> ViewGroup) : BindingNode {

@Inject lateinit var vm: ViewModel

fun build() {

depdency.inect(this)

binding(parentView(), vm)

}

fun dependency(dependency : ParentComponent) {

this.dependency = dependency

return this

}

}
Node 빌드 코드 - 3 : BindingNode
open class BindingNode {

fun binding(parentView: ViewGroup, vm: ViewModel) {

val binding = ViewBinding.inflate(vm.layoutId, parentView)

binding.setVariable(BR.vm, vm)

}

}



abstract ViewModel {

val layoutId:Int

}
Node 빌드 코드 - 4 : Root Parent XML
<FrameLayout>

<FrameLayout

android:id="@+id/parent1"/>

<FrameLayout

android:id="@+id/parent2"/>

</FrameLayout>
●장점
Node 의 제어를 통해 View 의 자유로운 플러그인이 가능
●단점
BackKey, Save/Restore Instance 등 다양한 처리 구현
MVP ~ Mid 사이즈 앱에는 과도한 적용
●권장
인터랙션이 많거나 View 의 자유도, 재활용이 많은 앱
정리
View 는 XML
DataBinding 필수요소
감지하기 어려운 뷰 변화는 ViewUsecase
Lifecycle, Result 위한 처리 필요
Resource 접근은 Wrapper 처리
Node 예제는 Optional
Q&A

[12]MVVM과 Grab Architecture : MVVM에 가기 위한 여행기

  • 2.
    정승욱 (Steve) 8 yrsAndroid Engineer GrabTaxi Geo Tech Leader GDE Singapore gdg slack : @seongug.jung (가입 요청 링크) medium : @jsuch2362
  • 3.
    MVVM 과 Grab아키텍쳐
  • 4.
  • 6.
    MVVM 에 대해이해한다고 전제로 공유
  • 7.
    내가 본 MVVM예시 class MainViewModel {
 val title: LiveData<String>
 } class MainActivity {
 val viewModel: ViewModel
 val textView: Textview
 fun onCreated() {
 viewModel.title.observe(this) {
 textVew.text = it
 }
 }
 }
  • 8.
    내가 본 MVVM예시 class MainViewModel {
 val title: LiveData<String>
 } class MainActivity {
 val viewModel: ViewModel
 val textView: Textview
 fun onCreated() {
 viewModel.title.observe(this) {
 textVew.text = it
 }
 }
 }
  • 9.
    문제점? class MainViewModel {
 valtitle: LiveData<String>
 } class MainActivity {
 val viewModel: ViewModel
 val textView: Textview
 fun onCreated() {
 viewModel.title.observe(this) {
 textVew.text = it
 }
 }
 }
  • 10.
  • 11.
    io17 Architecture Component데뷔 구글의 예시와 쏟아지는 개발자 예시들 유사한 이름의 MVVM 에 주목
  • 12.
  • 13.
    MVVM 은 AAC의 ViewModel 연관성이 없다
  • 14.
  • 15.
    ACC 의 ViewModel Activity/Fragment의 Lifecycle 의존성 낮추는 것 LiveData 는 Repository 로부터 데이터 변화 반응/적용 ViewModel 은 LiveData 로부터 View 에 필요한 데이터를 관리 View 에 필요한 데이터를 관리하기 위함
  • 16.
    ViewModel 그 자체의의미는 맞으나 MVVM 은 다른 것
  • 17.
    MVVM 에서 View- ViewModel 관계 View 와 ViewModel 의 연결을 최소화 ViewModel 은 데이터의 변화를 View 에 전달 View 는 화면 정보의 변화를 ViewModel 에 전달 View 는 XML, Activity/Fragment 가 아니다
  • 20.
  • 21.
    이렇게 되어야 합니다 classMainActivity {
 val viewModel: ViewModel
 val textView: Textview fun onCreated() {
 viewModel.title.observe(this) {
 textVew.text = it
 }
 }
 } class MainActivity {
 val viewModel: ViewModel
 fun onCreated() {
 dataBinding.setVariable(BR.vm, viewModel)
 }
 }
  • 22.
    이렇게 되어야 합니다 classMainActivity {
 val viewModel: ViewModel
 fun onCreated() {
 dataBinding.setVariable(BR.vm, viewModel)
 }
 }
  • 23.
  • 24.
  • 25.
  • 26.
    문제 사항들 1. Lifecycle 2.Databinding 으로 다 표현하기 힘든 View 이벤트 3. Resource 등 Context 를 접근해야 하는 경우
  • 27.
  • 28.
    RxLifecycle open class RxActivity{
 val rxbinder: RxBinder
 fun onCreated() {
 rxbinder.apply(ON_CREATED)
 }
 } class ViewModel(val rxbinder: RxBinder) {
 init {
 rxbinder.bind(ON_DESTROYED) {
 Observable....
 }
 }
 } class RxBinder {
 val map: Map<Event, CompositDisposable>
 fun apply(lifeEvent: Event) {
 map[lifeEvent]?.clear()
 }
 fun bind(lifeEvent: Event, body: () -> Disposable) {
 map[lifeEvent].add(body())
 }
 }
  • 29.
  • 30.
    ViewUsecase class GetVisibleAreaUsecase(view1) {
 funobserve(): Observable<Rect> {
 return Observable.create { e ->
 e.onNext(view1.height)
 view1.addGlobalLayoutChange {
 e.onNext(view1.height)
 }
 }.distinctUntilChanged()
 }
 } class ViewModel(usecase:GetVisibleAreaUsecae) {
 init {
 usecase.observe().subscribe {
 /* do something*/
 }.bindUntil(rxbinder)
 }
 }
  • 31.
  • 32.
    ResourceProvider class ResourceProvider(context: Context){
 fun string(resId: String) = context.resource.getString(resId)
 }
  • 33.
  • 34.
    Activity Result class Activity{
 val resultManager
 fun onActivityResult() {
 resultManager.onResult()
 }
 } class ResultManager {
 fun listen(observer)
 fun onResult()
 } class ViewModel(resultManager) {
 init {
 resultManager.listen {
 // do something
 }
 }
 }
  • 35.
  • 36.
    대략... class ViewModel(rxbinder, usecase,resultManager) {
 init {
 // many things are here
 }
 } class Activity: ResultableRxActivity {
 fun onCreated() {
 dataBinding.setVariable(BR.vm, viewModel)
 }
 }
  • 37.
  • 38.
  • 39.
    Activity 다시보기 class MainActivity:RxResultableActivity {
 @Inject lateinit var vm: MainViewModel
 fun onCreated() {
 dataBinding.setVariable(BR.vm, vm)
 }
 }
  • 40.
    껍데기 뿐인 Activity 하는역할은 DataBinding 과 DI
  • 41.
    View 에 대해다시 생각하기
  • 42.
    단일 Layout Layout 을조각내보기
  • 44.
    이 화면에 몇개의뷰 조각이 있을까요?
  • 46.
  • 47.
    각 Unit 을Node 로 구분 Node 가 구성될 때 ViewModel 을 바인딩
  • 48.
    Node 빌드 코드- 1 : Activity class ConfirmActivity {
 fun onCreated() {
 BackButtonNode({parentView}).dependency(root).build()
 TaxiTypeNode({parentView}).dependency(root).build()
 ExtraInfoNode({parentView}.dependency(root).build()
 PayConfirmNode({parentView}).dependency(root).build()
 // ..기타 등등....
 }
 }
  • 49.
    Node 빌드 코드- 2 : TaxiTypeNode class TaxiTypeNode(parentView:() -> ViewGroup) : BindingNode {
 @Inject lateinit var vm: ViewModel
 fun build() {
 depdency.inect(this)
 binding(parentView(), vm)
 }
 fun dependency(dependency : ParentComponent) {
 this.dependency = dependency
 return this
 }
 }
  • 50.
    Node 빌드 코드- 3 : BindingNode open class BindingNode {
 fun binding(parentView: ViewGroup, vm: ViewModel) {
 val binding = ViewBinding.inflate(vm.layoutId, parentView)
 binding.setVariable(BR.vm, vm)
 }
 }
 
 abstract ViewModel {
 val layoutId:Int
 }
  • 51.
    Node 빌드 코드- 4 : Root Parent XML <FrameLayout>
 <FrameLayout
 android:id="@+id/parent1"/>
 <FrameLayout
 android:id="@+id/parent2"/>
 </FrameLayout>
  • 52.
    ●장점 Node 의 제어를통해 View 의 자유로운 플러그인이 가능 ●단점 BackKey, Save/Restore Instance 등 다양한 처리 구현
  • 53.
    MVP ~ Mid사이즈 앱에는 과도한 적용 ●권장 인터랙션이 많거나 View 의 자유도, 재활용이 많은 앱
  • 54.
    정리 View 는 XML DataBinding필수요소 감지하기 어려운 뷰 변화는 ViewUsecase Lifecycle, Result 위한 처리 필요 Resource 접근은 Wrapper 처리 Node 예제는 Optional
  • 55.