본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[android] ViewModel & LiveData 의 pattern & anti-pattern

by 돼지왕 왕돼지 2021. 1. 22.
반응형



Views and ViewModels


Distributing responsibilities


-

이상적으로 ViewModel 은 Android 에 대한 어떤 것도 알아서는 안 된다.

이는 testability 를 늘려주고, 더 안전하며, module 화하기 편해진다.

이를 가장 쉽게 확인할 수 있는 방법은 android.arch.* 외에는 android.* import 가 없어야 한다.



-

조건문, loop, 동작 결정 등은 Activity, Fragment 가 아닌 ViewModel 이나 다른 layer 에서 수행해야한다.

View 는 어떻게 data 를 display 할지만 신경쓰고, user event 를 ViewModel 이나 Presenter 로 전달하는 역할만 해야 한다.

이것이 Passive View patten 이다.




View references in ViewModels


-

ViewModel 들은 activity 나 fragment 와 다른 scope 을 보인다.

ViewModel 이 살아있고 동작하는 동안, activity 는 어떤 lifecycle state 든 들어갈 수 있다.

ViewModel 이 인지하지 못하는 동안 Activity 나 Fragment 가 destroy 되고 다시 create 될 수 있다.



-

View 에 대한 참조를 ViewModel 로 전달하는 것은 아주 위험하다.

ViewModel 이 network 에 데이터를 요청하고, 그 결과가 나중에 온다고 해보자.

그럼 더 이상 valid 하지 않은 View 에 대한 참조가 이루어져서 memory leak 또는 crash 를 야기할 수 있다.



-

추천되는 ViewModel 과 View 간의 컴 방법은 observer pattern 이다.

LiveData 나 observable lib 을 사용하는 것이다.




Observer Pattern


-

View 가 ViewModel 을 observe 하는 것이다.

ViewModel 은 android 에 대해 모르기 때문에, android 가 view 를 얼마나 자주 죽이는지 이런것은 알지 못한다.

이는 config change 에 대해 ViewModel 은 영향을 받지 않으며, 따라서 rotation change 등이 발생했을 때 requery 같은 것을 안 해도 된다는 장점을 가질 수 있다.

앞서 설명한 memory leak 을 방지할 수 있고, 존재하지 않는 view 에 대한 NPE 도 피하기 좋다.





Fat ViewModels


-

ViewModel 이 너무 많은 code 나 책임을 가지고 있다면 다음을 고려해보라.


몇몇 로직을 presenter 로 이관해보자. 

Clean Architecture 의 Domain layer 를 추가해보자. 이를 통해 testability 와 architecture 유지보수력이 좋아진다. main thread 에 대한 점유도 빨리 놓아버리는 장점도 생긴다.





Using a data repository


-

대부분이 다음과 같은 data source 를 가지고 있다.


1. Remote; network 나 cloud

2. Local : db 나 file

3. In-memory cache



-

presentation layer 에서 알지 못하는 data layer 를 갖는 것은 좋은 방법이다.

cache 를 쓰는 것, db 와 sync 맞추는 것, network query 하는 것은 쉽지 않은 문제이다.

이들은 별도의 repository class 로 관리하고 entry point 를 하나로 관리하는 것은 복잡함을 관리하는 데 좋다.





Dealing with data state


-

ViewModel 을 통해 노출된 List item 을 가진 LiveData를 observe 한다고 해보자.

이 때 View 는 어떻게 data loading 중, network error, 실제 empty 인 list 등의 상태를 구분할 수 있을까?


이는 LiveData<DataState> 라는 것을 따로 관리함으로써 할 수 있다.

DataState 변화에 따라 observe callback 이 불릴거고, 그 값을 기준으로 분기하는 로직이 있으면 되겠다.





Saving activity state


-

LMK 등의 상황에서는 ViewModel 도 제거되는데 UI state 를 저장하고 복구하기 위해서 onSaveIntanceState 와 ViewModel 모두를 사용할 수 있다.

관련된 내용은 다음 링크를 참고하자

https://medium.com/androiddevelopers/viewmodels-persistence-onsaveinstancestate-restoring-ui-state-and-loaders-fc7cc4a6c090





Events


-

activity 가 rotate 되면서 recreate 되고, 새롭게 observe 를 시작하면, LiveData 는 old value 를 바로 전달한다.

이것이 문제를 야기할 수 있는데, 이를 다른 lib 이나 architecture component extension 으로 풀기보다는, design 문제로 여겨야 한다.

event 는 state 의 일부로 취급해야 한다.

이에 대한 자세한 정보는 다음을 참고하자

https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150





Leaking ViewModels


-

reactive 패러다임은 안드로이드에서 잘 작동한다.

LiveData 는 이의 key component 이며, activity 와 fragment 가 이를 observe 한다.

이상적으로 ViewModel 은 observe 하는 view 가 없다면 함께 사라지는 것이 맞다.



-

ViewModel.onCleared() 가 불리면 repository 에게 ViewModel 에 대한 callback 을 버리라고 하면 된다.

Repository 에서는 WeakReference 나 EventBus 를 사용하는 방법이 좋다.

LiveData 를 사용해서 Repository 와 통신하는 것이 좋다. View 에도 LiveData 를 넘겨주는 것이 좋다.





LiveData in repositories


-

ViewModel 의 leak 과 callback hell 을 피하기 위해서, repository 가 observe 될수도 있다.

ViewModel 에서 repository 를 LifecycleOwner 없이 어떻게 구독할 수 있을까?

Transformation 을 사용하면 쉽다.

Transformations.switchMap 은 다른 LiveData 객체들의 변화에 반응하는 새로운 LiveData 를 쉽게 만들 수 있게 해준다.

그리고 이것은 Observer 의 Lifecycle 정보를 chain 을 따라 전파하는 데도 좋다.





Extending LiveData


-

LiveData 를 쓰는 가장 일반적인 방법은 ViewModel 안에서 MutableLiveData 를 사용하는 것이다.

그리고 그를 LiveData 의 형태로 노출하여 observer 에서는 immutable 로 인식하게 하는 것이다.




When not to extend LiveData


-

onActive() 를 사용하여 data 를 load 하는 service 를 시작시킬 수 있다. 

그러나 적합한 이유 없이는 LiveData 가 observe 되기까지 기다릴 필요가 없다.

일반적인 패턴은 ViewModel 에 start() 함수를 추가해서 가능한 빨리 부르는 것이다.

또는 load 를 시작시키는 property 를 set 시킬 수도 있다.





Summary


-

Good Design 추구하기

    activity, fragment 에 logic 을 최소화하자.

    UI 에 data 를 밀어넣기보다는, UI 가 변화를 observe 하도록 하라.

    책임을 분산하고 필요하다면 domain layer 를 추가하라.

    single-point entry 를 가진 data repository 를 추가하자.

    data 의 상태에 대한 정보를 wrapper 나 LiveData 를 사용하여 노출하자.

    event 를 상태의 일부로 디자인하자.

    leak 과 얼마나 operation 이 오래걸리는지 등의 edge case 를 고려하여 architecture 를 수정하자.

    ViewModel 안에서 Lifecycle object 가 필요하다고 생각된다면, Transformation 이 해결책이 될 수 있다.

    


-

Bad Design 피하기

    ViewModel 이나 Presenter 들이 Android framework 에 대해 알게 하지 말라

    ViewModel 이 View 를 ref 하도록 하지 말라.

    ViewModel 에 상태나 관련 데이터 저장에 대해 중요한 로직을 넣지 말아라. ViewModel 을 통한 call 이 마지막 Call 이 될 수 있다는 것을 기억하라. (돼왕: 정확히 이해가 안 되어 원문을 추가한다. Don't put logic in the ViewModel that is critical to saving clean state or related to data. Any call you make from a ViewModel can be the last one.)

    LiveData 를 상속할 일은 왠만해서는 없다. activity 나 fragment 가 ViewModel 에 data loading 할 시간이라고 알리게 하라.



-

참고 : https://medium.com/androiddevelopers/viewmodels-and-livedata-patterns-antipatterns-21efaef74a54






반응형

댓글