안드로이드 MVVM 패턴 따라 하기 - 시작하기 전에



개인 광고 영역

이 글은 안드로이드에서 MVVM을 어떤 식으로 접근하는지 고민해 보고, 따라 하기 위한 글이다.

2016년에 MVP 무작정 따라 하기 시리즈를 작성했었는데, 이제서야 MVVM 무작정 따라 하기 시리즈를 작성하려고 한다.

이미 많은 곳에서 MVVM을 활용하고 있고, 더 새로운 아키텍처를 찾기도 한다.

사실 이 시점에 MVVM 따라 하기를 작성하는 건 큰 도움이 되지는 않을 것이다.

하지만 그 중간 과정에서 주의해서 활용되어야 할 부분은 분명히 있다. 이런 부분을 기반하여 새로운 MVVM 따라 하기를 작성하게 되었다.


이 글에서 알아볼 내용

  • MVVM이 뭘까?
  • MVP를 알아야 할까?
  • MVP와 MVVM이 어떻게 다를까?


MVC

패턴이란 건 발전한 형태이다. 전혀 새로운 형태가 아닌 이유가 있어 발전하였다.

MVC 패턴은 오래되었지만 여전히 스프링에서 활용되고있다.

  • Model
  • View
  • Controller

Controller에서 이벤트가 생성된다. 이 이벤트에 따라 Model에 업데이트를 요청하고, View에게 전달한다. View는 Model로부터 업데이트 받고, 갱신한다.

View에서 Controller의 이벤트를 통지받고, Model도 함께 사용한다.

이렇게 활용되면 Model에 대한 테스트는 작성하기 쉽지만 View와 Controller의 테스트는 복잡해진다.

우리가 아는 View는 사용자가 보고있는 화면이다. 이 화면은 지속적으로 수정이 이루어지니 그에 따른 작성한 테스트 코드 업데이트 시간이 부족해진다.

최소한 비즈니스 로직이라도 분리되면 테스트 가능성이 열리고, View가 아닌 로직에 대한 테스트가 가능해진다.


MVP와 MVVM

그래서 나온 것이 MVP 패턴이다.

MVP 패턴에서는 MVC의 Controller 역할을 View에서 처리하게 된다. 그리고 로직을 분리한 였고, Model에 대한 직접 접근 역시 고리를 끊었다.

여기서는 제목을 MVP와 MVVM으로 달았는데 이 둘의 패턴 차이가 과연 있을까?

먼저 안드로이드에서의 패턴을 살펴보자.

안드로이드의 액티비티는 무슨 패턴을 가졌을까?

사실 가진 패턴은 없다. 액티비티는 Model도 Controller도 모두 접근 가능했기 때문에 MVC 패턴을 끼어 넣어 설명하기는 어려웠다.

DataBinding을 사용하였다면 Binding이 Controller의 역할을 하는 것이고, View는 xml과 Activity로 분리해 설명할 순 있었다.

하지만 처음 태생부터 이런 패턴을 고려해 만들어진 것이 아니기에 어떠한 조건도 맞지는 않는다.

필자의 경우 MVP/MVVM 패턴부터 Android에 적합하다고 생각한다.

  • Activity는 View로 분리할 수 있다. DataBinding/ViewBinding을 활용하더라도 Controller 포함되어 MVP/MVVM의 View에 해당한다. 말 그대로 그냥 View라고 표현할 수 있다.
    • 당연히 Resource에 접근하는 형태는 모델에 해당하는데 이는 예외로 둔다.

그럼 MVP의 Presetner와 MVVM의 ViewModel의 역할은 뭘까?

MVP의 Presetner와 MVVM의 ViewModel의 역할은?

당연하게도 비즈니스 로직을 처리한다. 이 경우 View와 1:1 관계로 강 결합 되었느냐? 유연하게 필요한 통지만 받을 수 있느냐의 차이가 생길 뿐이다.

그렇다면 자연스럽게 뷰 로직을 그리기 위한 Presetner와 ViewModel 이란 걸 알 수 있다.

P와 VM은 사실 별거 없다. 하지만 보통 이 부분을 이해하는 데 있어 높은 난이도가 요구되는데

  • 코드를 굳이 이렇게까지 나눠야 하는가?
  • 단순히 버튼 이벤트로 인해 배경을 변경해야 하는데 이것도 P와 VM에 넘겨야 하는가?
  • 이런 이벤트의 View 갱신 데이터를 P와 VM이 굳이 통지해야 하는가?

그럼 테스트를 한다고 생각해 보자

필자가 생각하는 MVP와 MVVM 핵심은 코드 분리와 함께 당연히 유닛 테스트도 동반되어야 한다는 점이라고 생각한다.

대부분 유닛 테스트를 생각지 않고, 액티비티 전환이 필요한데 함수에 Context를 넘겨 바로 화면전환시키면 되는 것 아닌가?

그럼 Context를 유닛 테스트하려면 어떻게 해야 할까?

Robolectric을 활용하면 간단하게 해결은 된다.

하지만 수십 개 수백 개의 유닛 테스트를 돌려야하는 상황에서 robolectric을 쓴다면? 대략 M1 맥에서 robolectric 테스트 준비 시간만 약 1초가 걸린다.

한두 개면 사실 상관없다. 하지만 모든 Presetner, ViewModel에서 돌려야 한다면? 이게 100개라면?

테스트가 가능해야 하는 게 답인가?

필자는 테스트하지 않더라도 테스트가 가능한 형태여야 한다고 생각한다. 이때의 테스트는 Mockito를 가지고서도 테스트가 가능해야 한다고 생각한다.

그리고 모든 함수가 테스트 가능해야 한다.


만능은 아니다.

그렇다 테스트가 가능해진다고 만능은 아니다. 하지만 최소한 Context를 넘기지 말자는 이야기를 하는 건 Context를 이용해 Activity/Fragment에서 할 수 있는 모든 걸 다 접근할 수 있어서이다.

그렇다면 굳이 왜 힘들게 Presetner와 ViewModel을 굳이 나누는 걸까?

아무래도 만족감일 수도 있다. 뭔가 나눴더니 액티비티 코드는 줄어들고, 그럼 그에 대한 만족도가 생긴다.

그렇다고 테스트에 집착할 필요도 없다. 테스트 커버리지가 100%라고 그 프로그램이 오류가 없을까?


다시 돌아가서

MVP와 MVVM에서 말하자고 하는 명확한 건

  • 테스트 가능하도록 설계하라
  • View 로직에 대한 검증을 실시할 것
  • MVP, MVVM을 퓨어 한 형태로 만들든, Android AAC-ViewModel을 활용해서 만들던 상관없다.

우리의 목표는

  • 코드의 분리
  • 재사용 가능한 코드의 가능성
  • 테스트를 통해 로직에 대한 오류를 예측할 것


초보라면 MVC부터 시작하라

처음 시작하는 개발자라면 절대로 MVP, MVVM부터 시작하지 않는 것이 좋다.

최소 액티비티에 코드가 다 들어가도 일단 거기서부터 시작해야 한다.

이유 없는 코드는 존재하지 않는다. 왜 이렇게 분리해야 할까를 함께 고민해라.

어차피 모두가 설계를 잘하는 것은 아니지만 최소한 이유를 알고 설명을 할 수 있다면 더 이유를 잘 설명할 수 있으리라고 생각한다.

모두가 MVVM 패턴을 써야 한다고 말하지만 필자는 그렇지는 않다고 생각한다.

다만 회사에 입사했다면 대부분이 MVVM 패턴을 활용하고 있다는 점은 명심하라.

  • MVVM 패턴에 AAC-ViewModel을 활용하고
  • Dagger, Hilt, Koin을 활용한다.
  • RxJava, Coroutines, Coroutines - Flow를 활용한다.
  • LiveData, Coroutines StateFlow, SharedFlow를 활용한다.
  • DataBinding을 활용한다.

이것만 적었지만 이미 알아야 할 개념이 넘쳐난다.

  • DI란?
  • 리액트 프로그래밍이란?
  • Thread와 Coroutines?
  • DataBinding을 왜 사용할까?
  • LiveData를 왜 사용할까?


천천히 알아보자.

이 글에서는 MVP와 MVVM의 전혀 다르지 않음을 정의하였다.

코드 분리 요건이 동일하기 때문에 다르지 않다고 정의하였을 뿐이다.

형태는 당연히 다르다.


MVVM으로 할 수 있는 것?

MVP로도 가능하다고 생각하지만 1:1 관계를 요구하는 MVP에 비해 MVVM은 유연하다.

클린 아키텍처인 UseCase를 만들어 이를 대신 활용할 수 있겠지만 이건 어디까지나 MVP의 공통 코드 처리 이슈로 생긴 하나의 패턴으로 본다.

MVVM에서도 이를 활용할 순 있지만 예를 들어 아래의 조건을 만족하도록 만든다면? 굳이 UseCase가 필요할까?

아이디와 비밀번호를 입력하는 UI를 4개 이상의 화면에서 그대로 활용해야 한다. UI도 동일하고 로직도 동일하다.

방법은 2가지가 있을 것이다.

UseCase를 활용하는 경우?

  • View는 공통화하지 않는다.
  • ViewModel 역시 공통화하지 않는다.
  • UseCase를 통해 두 값을 입력받으면, Boolean 값을 업데이트하도록 만든다.

이 경우의 단점은 뭘까?

  • 공통화하지 않았기 때문에 동일한 코드가 4곳에 포함되어야 한다. 로직이야 UseCase가 대신할 것이니 연결점만 만들면 되겠다.
  • View 로직을 처리하는 UseCase인데, 이건 View에 포함되어야 할 것인가? Model에 포함되어야 할까?
    • View 로직을 처리하니 당연히 View UseCase이다. 이게 Model의 UseCase에 포함되어야 할 이유는 전혀 없다.

1개의 뷰와 1개의 ViewModel에서 처리한다.

  • View에 로그인 처리 부분만 공통화 시킨다.
  • ViewModel은 이 View 내용을 포함하여 뷰 로직을 처리한다.

이 경우의 장/단점은 뭘까?

  • View만 연결하면 나머진 연결할 필요 없다.
    • 필요한 이벤트는 Repository에서 처리한다.
  • XML로 작성하는 경우에는 커스텀 레이아웃을 만들어야 한다.
    • Compose로 하면 매우 간단해질 수 있다.

어차피 두 방식 모두 장/단점은 존재한다.

개인적으론 후자를 선호한다. 정말 MVVM을 이용하면 1:1 형태의 뷰와 뷰 모델까지도 만들 수 있기 때문이다.

뷰를 여러 곳에 그대로 활용할 수도 있다.

또 다른 예로

항상 닫는 버튼이라면?

  • 하나의 버튼과 뷰 모델의 행동만 정의하고 이를 그대로 활용

할 수 있다. 얼마나 편한가. 테스트도 하나에서만 끝나고, 뷰만 연결하면 끝나지 않을까?

MVVM 패턴으로 할 수 있는 최대한이라고 생각한다.

개인적으로 이미 이런 방식의 코드들을 적용해 사용하고 있다.

단순히 Dagger로 @Inject하고, 그냥 호출만 하면 끝난다. 나머지 로직은 이미 작성되어 있는 코드에서 동작한다.


어차피 우리는 팩토리 패턴을 만든다.

그렇다 우리는 디자인 패턴 중 하나인 팩토리 패턴을 하기 위한 과정일 뿐이다.

얼마나 잘 조립하고 얼마나 공통화 시켜 조립할 수 있느냐의 관점이다.

필자는 MVVM이 어렵다고 생각한다. 팩토리 패턴도 설계가 매우 중요하기 때문이다.

1:1 형태의 MVVM 뷰가 많아지면 질수록 팩토리에 가까워진다.

단순히 조립하면 뷰가 하나 나오고, 그에 따른 로직까지 처리되기 때문이다.

상상하면 얼마나 아름다운가


개인적으론 MVI 패턴에 대한 이해도는 없다.

하지만 대부분의 샘플 코드는 MVVM이라고 해도 무방하다는 건 알고 있다. 그건 MVI의 일부를 가져와서 MVI라고 표현하는 정도이고, Context를 넘겨 바로 호출하는 경우도 보았다.

MVI라면 최소한

  • View가 스스로 모든 상태를 파악할 수 있어야 한다.
    • 성공, 실패, 동작 중인지, 동작 가능한지 와 같은 형태이다.

MVVM은 어떻게 보면 공통화 시켜둔 코드가 많아지면 MVI의 일부분이 될수도 있다고 생각한다.

이미 필자가 작성한 코드를 MVI라고 설명할 수도 있을지도 모른다.

  • Close 시켜야 할지 스스로 결정하고, Close 가능한지도 스스로 알고, 오류가 났는지도 스스로 알 수 있다.


마무리

이 시점에 MVVM 따라 하기 같은 글을 작성하는 건 큰 의미는 없을 수 있다. 하지만 과거 MVVM이 뭘까?에 대한 생각과 개념은 지금과 다르지 않다.

도구가 달라졌을 뿐 그 부분이 크게 다르지 않다.

이 글이 MVVM을 하는 데 있어 도움이 되었으면 한다. 앞으로 몇 개의 글을 작성할진 모르지만 MVVM 무작정 따라 하기 시작하겠다.



About Taehwan

My name is Taehwan Kwon. I have developed Android for 6 years and blog has been active for eight years.

Comments