[Android] MVVM 핥아보기(1)

양현진·2022년 2월 25일
0

Oh My Android

목록 보기
7/22
post-thumbnail

전 프로젝트에서 한 액티비티에 코드를 때려 박다 보니 코드를 추가하거나 오류가 났을 때 수정하려 하면 콘센트의 구멍 찾는 거 마냥 찾기가 어려웠다. 그리고 이번 캠핑 앱을 시작하기 전, 현재 안드로이드에서 자주 쓰이는 라이브러리나 기술들을 적용해 보고 싶어 몇 개를 선택했고 그중 하나가 MVVM 이었다. 이 외에 것들은 '이걸 사용할 줄 알아야 개발자인가? 나도 해봐야겠네'라는 생각이었는데 MVVM 관련 포스팅을 읽고 대강 이해하는 순간 개발을 여태 하면서 나에게 필요했던 것을 처음 찾게 되어 설렜고 가장 먼저 적용해 보려 했었다.

MVVM

디자인 패턴의 한 종류로써 모델(M), 뷰(View), 뷰 모델(ViewModel)로 나눠 각 레이어 간 의존성을 줄여 테스트, 유지 보수, 재사용이 쉬어진다.

그림만 보면 도중에 껴있는 ViewModel은 하는 것도 없이 전달만 하는데 왜 있는 거지 했는데 비즈니스 로직을 담당하는 거였다. 그리고 5번의 관찰부분이 MVVM에서 중요한데, 밑에서 글을 적어야겠다.

디자인 패턴

특정 문맥에서 공통적으로 발생하는 문제에 대해 재사용 가능한 해결책이다. 소스나 기계 코드로 바로 전환될수 있는 완성된 디자인은 아니며, 다른 상황에 맞게 사용될 수 있는 문제들을 해결하는데에 쓰이는 서술이나 템플릿이다.

디자인 패턴은 영어 그대로 설계 유형, 설계들이라 해석되어 느낌이 왔지만 MVVM을 검색하면 나오는 의존성, 유지 보수, 재사용들은 추가적인 설명이 없어 뜻하는 의미가 정확히 무엇인지 궁금했다. 이 키워드들은 객체지향 프로그래밍과 관련되어 있으니 이곳에서 좀 더 파고들어가 보겠다!

View

버튼을 누르거나, 스크롤을 하거나, 텍스트 입력 등 사용자로부터의 이벤트 알림과 UI를 담당한다.
여기서 추가로 DataBinding과 BindingAdapter를 사용해서 View를 한 번 더 쪼개는 식으로도 쓰인다. DataBinding을 간략하게 말하자면
선언적 형식으로 레이아웃의 UI 구성요소를 앱의 데이터 소스와 결합할 수 있는 라이브러리이다. 사용 이유로는 UI 관련된 코드를 xml 파일에 작성함으로써 Activity나 Fragment에서 많은 UI 호출을 삭제할 수 있어 코드가 더욱 단순화되고 유지관리 또한 쉬워진다. 또한 메모리 누수 및 null 포인터 예외를 방지할 수 있다. 더 자세한 내용을 쓰려 했지만 제목이 핥아보기라 추가적인 내용과 BindingAdapter는 다음 포스팅에서 작성해야겠다.

ViewModel

View에서 받은 이벤트를 가공시켜 Model로 요청하고, Model로부터 요청한 데이터를 받아 View가 관찰할 수 있게 데이터를 담는다.
여기서 관찰할 수 있게 담는곳이 Android ACC의 LiveData이다!

Android ACC

Google I/O 2017에서 발표한 테스트와 유지보수가 쉬운 앱을 디자인할 수 있도록 돕는 5가지의 라이브러리의 모음이다.

  • Lifecycles
  • LiveData
  • ViewModel
  • Room
  • Paging

ViewModel에서 주로 (acc)ViewModel과 LiveData를 사용하는데, 이름이 같은 것이 있다(바로 ViewModel). 흔히 이 2개는 같은 것이 아니라 다른 거라고 말한다. 근데 다르다고만 하고 별 설명이 없어 초반엔 'ㅇㅋ 다르대 다음' 하고 넘어갔던 기억이.. 그럼 뭐가 왜 다를까?
결론적으로 말하자면 Activity가 예기치 못하게 onDestroy되고 다시 onCreate되는 상황

  • Stop 된 상태에서 오랫동안 사용하지 않을 때
  • Stop 된 상태에서 전면에 있는 Activity가 더 많은 리소스가 필요할 때 메모리 확보를 위해
  • 사용자가 화면을 회전 시켰을 때

이 있는데 이런 경우 Activity가 자체적으로 가지고 있던 여러 가지 상태 정보들은(ex - textview 등) 삭제가 되어버린다. 이러한 추가적인 데이터를 저장하고 싶다면 onSaveInstanceState() 콜백 함수를 사용할 수도 있지만, MVVM에 걸맞게 ViewModel에 (acc)ViewModel을 상속시키면 onCleared()라는 콜백 함수를 사용할 수 있게 된다.

그림을 보면 onDestroy의 바로 하단에 onCleared가 위치해 있는데 이 뜻은 Activity가 onDestroy후에 onCleared가 실행된다.

위 그림과 같이 Activity shut down까지 가지만 않으면 onCleared가 호출되지 않아(추측) 위 3가지의 상황에 대비하여 ViewModel에 데이터들을 편안하게 저장할 수 있는 방식이다. 추가로, 어떤식으로 Activity의 생명주기를 아는가에 대한것은

public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }
    
,,,

viewModel = ViewModelProvider(this)[MainViewModel::class.java]

Activity에서 ViewModel을 생성할 때 해당 Activity의 lifecycleOwner를 넘겨주는 방식이다. 이 뒤는 갓구글이 알아서 처리,,

Model

ViewModel에서 받은 데이터를 외부 서버(ex - http request), 내부 DB(ex - room)에게 요청하고 다시 데이터를 받아 ViewModel로 넘긴다. 여기서도 Repository 패턴을 사용하는 예제들이 많았다. 자, 이건 또 왜 쓸까?
ViewModel은 어디서 데이터를 가져오는지 알 필요가 없어 데이터 로직을 분리할 수 있다라고 한다. 코드로 보게 되면

class Repository(
    private val service: CampingService?,
    private val mDataBase: MDataBase?
    ) {

    // Retrofit
    fun getFromServer(): Single<Response> = service?.getList()

    // Room
    fun getFromDataBase() = mDataBase?.favoriteDao().getFavorite()
}

class DetailViewModel: ViewModel() {

    fun getImageList() {
        addDisposable(
            repository.getFromServer()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        setList(it)
                    },
                    {
                        onFailLoad()
                        Log.e(ERROR, "DetailFragmentViewModel: ${it.message}")
                    }
                )
        )
    }

    fun getFavoriteList() {
        addDisposable(
            repository.getFromDataBase()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        setList(it)
                    },
                    {
                        onFailLoad()
                        Log.e(ERROR, "DetailFragmentViewModel: ${it.message}")
                    }
                )
        )
    }
}

이런 식으로 Repository 클래스를 호출하는거로 ViewModel에서 server나 db를 구분할 필요가 없다는 것! 만약 안쓰게 되면?

class DetailViewModel: ViewModel() {

    fun getImageList() {
        addDisposable(
            service?.getList()
                ...
        )
    }

    fun getFavoriteList() {
        addDisposable(
            mDataBase?.favoriteDao().getFavorite()
                ...
        )
    }
}

요런 느낌? 정도로 이해할 수 있을 것 같다. 정리하자면 한번 더 레이어, Repository라는 데이터 소스 중재자를 두어 복잡한 로직을 단순화 한다는 뜻이다.

이 정도로 개념 핥기식으로 이번 포스팅을 끝내고 다음 포스팅에서 MVVM 개봉하기로 넘어가 DataBinding, BindingAdapter와 실제 적용했던 코드를 더 나열하여 이어서 작성해야지


profile
Android Developer

0개의 댓글