[Android] 더 나은 architecture을 위한 필수템, ViewModel

유영석·2022년 9월 9일
0

잡다한 정보

목록 보기
10/15
post-thumbnail

Android 개발자로 추후 진로를 결정할 생각은 없지만, 어찌다 보니 지금 하고 있는 프로젝트가 안드로이드 어플리케이션이네요. (그래도 아직은 생각 없습니다)
나름 Google Playstore에도 올려서 서비스를 진행할 어플리케이션이라 안정성을 신경써야 하기 때문에 ViewModel 이란 개념을 도입해서 개발하기로 했습니다.

새로운 개념이니만큼 이해하기 쉽게 그리고 개발을 위해 최소한으로 필요한 만큼만 설명을 하는 방향으로 잡겠습니다.😃

MVVM이란?

ViewModel을 알기 전 MVVM이란 개념을 알아야하는데, ViewModel이 여기서 파생되었기 때문입니다.


(발그림 죄송합니다ㅎㅎ)

MVVM(Model-View-ViewModel) 패턴은 하나의 코드에서 사용자에게 보여지는 GUI 부분, 즉 View 를 비즈니스 로직(모델)로부터 분리하여 독립적인 역할로써 존재하게 하는 Software Architecture 패턴입니다. 쉽게 말해서 GUI와 관련된 코드들을 관련없는 다른 코드들과 분리시켜서 독립적 으로 존재하게 하고 싶다는 겁니다.
기술이 발전할수록 개발되는 프로그램들은 점점 거대하져 가기에 기능에 있어 역할들을 세분하여 독립적이게 하는 것도 점점 중요한 주제가 되어가고 있죠? MVVM은 이 View에 있어 그런 니즈를 충족시키위해 태어났다고 보시면 됩니다.

  1. View에서는 사용자가 스크린에서 보이는 UI 적인 요소만 독립적으로 정의합니다. 오직 어떻게 보일지에 대한 것만 구현한다는 뜻이죠. ViewModel의 변화에 맞춰 사용자에게 보이는 부분에도 변화를 주는 것이지요.
  2. Model에서는 해당 프로그램에서 사용할 Data 와 그 Data에 대한 처리들을 정의합니다. 네트워크 통신과 관련되어 있는 부분이라 생각하면 편할겁니다. 서버와 커뮤니케이션 하여 data들을 보내고 가져오고 계산하는 등의 과정을 독립적 으로 맞게 되는 것입니다.
  3. ViewModel에서는 위의 그림과 마찬가지로 이 ViewModel다리 가 되어준다고 생각하면 됩니다. Model과 커뮤니케이션하여 데이터들을 가져와 이를 알맞게 가공하여 View가 오직 이 데이터들을 표현 하는데에만 집중할 수 있도록 조력자 역할을 합니다.

이렇게 분리해 놓으면 작업이 병렬적으로 진행될 수 있게 되어 테스트와 유지 및 보수 등이 한결 효율적이게 진행되므로 전체적인 안정성에 크게 기여하게 되는 것입니다.

Android의 ViewModel

Android에서 View는 UI 컨트롤러라고 부릅니다. 저희가 이미 아는 Activity, Fragment가 이 UI 컨트롤러에 포함되는 것이죠. 앞선 ViewModel의 장점과 더불어 Android내에서 ViewModel은 추가적으로 한 가지의 장점을 더 가지게 되는데 바로 Life cycle(생명주기) 입니다.

UI 컨트롤러(View)에서 관련 데이터를 저장하고 있게 되면 당연하게도 해당 UI 컨트롤러가 제거될 때 관련 데이터 또한 제거되게 되겠죠?? 이렇게 되면 UI 컨트롤러가 재생성 되게 될 때 관련 데이터 또한 다시 불러와야 하는 비효율적 문제가 생기게 됩니다. 대표적인 예시가 회전인데 Android에서 화면을 회전하게 될 때 UI 컨트롤러가 삭제 후 재생성이 되는데 사용하는 관점에서는 화면만 달라지는 것이 전부여야 하지만 UI가 재생성 되면서 데이터들까지 달라지는 참사가 생기게 되는 것이죠. (기존에는 Bundle을 사용해서 해결했지만, 이제는 성능의 한계에 부딪히게 되버렸다고 합니다.😅)

이와 같이 UI 컨트롤러와 상관없이 ViewModel이 UI 컨트롤러의 LifeCycle을 모두 포함하는 독립적인 Life Cycle을 갖게 되면서 UI 컨트롤러의 변동에도 데이터는 유지할 수 있게 되는 가장 큰 장점이 생기게 됩니다. 스마트폰의 성능이 향상되고 사용자의 활용이 복잡해지면서(어플리케이션 실행 중에 이것 저것 다른 활동을 하는 경우) ViewModel은 선택이 아닌 필수가 되었다고 할 수 있겠죠???🥴

Android에서 구현되는 MVVM 패턴은 다음과 같습니다.

MVVM 개념에서 Activity/FragmentView임은 위에서 설명했고, 추가적으로 RepositoryModel이 되는 것입니다. Repository에서 또 데이터 베이스와 웹 서비스 관련 Model들과 소통이 이루어짐을 보여줍니다. 위에서 생소한 것이 LiveData일 것일 텐데요.

LiveDataViewModel에서 데이터를 담고 있게 해주는 Holder 클래스입니다. LiveData는 UI 컨트롤러가 Data의 변경을 관찰(모니터링) 할 수 있는 클래스입니다. 따라서 ViewModel이 이 LiveData라는 것을 통해 UI 컨트롤러에게 '야! 데이터 바뀌었어! 일해!!!"라고 해주는 것이지요. LiveData가 다른 클래스와 다른 가장 큰 특징은 LifeCycle을 인식한다는 점입니다. UI 컨트롤러의 LifeCycle에 따라 Active한 상태에서만 Model과 커뮤니케이션하여 Data를 업데이트함으로써 동기화, 메모리, 비정상 종료, 자동화 등의 많은 측면에서 엄청난 장점을 가지게 됩니다.

Android에서 ViewModel(MVVM) 구현

제가 찾기로 한국 블로그에 설명은 많은데 코드를 보여주며 설명해주는 곳이 많지 않았습니다. 그래서 저와 함께 간단히 살펴볼텐데요. 외국 블로그에서 여러 능력자분들이 쓰신 여러가지 방식의 코드들이 있습니다만, 저는 제가 직접 프로젝트에 사용하였던 방식으로 예시를 들어보겠습니다.(따라서 다소 코드에 편향성이 있을 수 있으니 하나의 예시 그 이하로만 생각해주셨으면 합니다.) 이 때 배웠단 간간한 다른 지식들까지 말씀드릴테니 천천히 따라와 보시죠! 간단하게 사용자 정보를 다루는 MVVM을 구현해보도록 하겠습니다.

가장 먼저, ViewModel 작성을 해봅시다. 사용자 정보에 대한 ViewModel이니 class이름을 UserViewModel이라 칭하겠습니다.

class UserViewModel : ViewModel() {
    private val users = MutableLiveData<ArrayList<User>>()
}

사용자 정보를 담은 User라는 데이터 클래스를 담은 ArrayListLiveData로 감싸고 있는 모습입니다. ArrayList<User>대신 어떤 타입이든 사용자가 필요한 자료형을 그때그때 넣어서 감싸주시면 됩니다.

이제 함수를 사용자 정보를 가져오는 함수를 만들어볼까요. Repository에 데이터 베이스로부터 사용자 정보를 가져오는 코드를 작성합니다.

object Repository {
    fun loadUsers() = callbackFlow {
        ...
        trySend(user)
    }
}

갑자기 callbackFlow 가 뭐냐 하실수도 있는 저와같은 분이 계실겁니다. 이 getUsers함수처럼 네트워크와 통신을 한다면 통신에 의한 시간이 걸리겠죠? 평소에는 괜찮다 하더라도 만약 이 시간이 내가 작성한 코드를 컴퓨터가 진행하는 속도보다 오래 걸린다면 문제가 생기는 것입니다. 이 통신은 코드 진행과는 별개로 (정확한 표현은 아니지만...) 일어나고 있기 때문에 user을 가져오기도 전에 프로그램은 이미 user을 가져온 이후의 코드들을 실행시키고 있는 것이지요. 그것을 막아주기 위해 kotlin에서는 이와 같은 방식으로 좀 그때그때마다 기다려달라는 의사를 전달하고 있는 것입니다. user라는 사용자 정보를 가져오는 것이 끝났다면 trySend로 데이터를 쏴줌으로써 이어서 진행하라는 의사까지 전달하는 것입니다. 이와 같은 방식을 callback 이라고 하는데 더 알아보고 싶으신 분들은 정말 많은 좋은 글들과 자료들을 참고하여 공부하시면 좋을 것 같습니다.

자 이제 UserViewModelRepository사이에 다리를 놓아주어야 할 것입니다. Repository에서 가져온 정보를 UI 컨트롤러에게 전달하는 함수를 작성해봅시다.

class UserViewModel : ViewModel() {
    private val users = MutableLiveData<ArrayList<User>>()

    fun getUsers() = viewModelScope.launch {
        val list: ArrayList<User> = arrayListOf()

        Repository.loadUsers().collect { user ->
            user?.let { list.add(user) }
        }
        users.postValue(list)
    }
}

getUsers가 하는 일은 Repository의 미리 써놓은 loadUsers를 통해서 사용자 정보를 가져와 LiveData에 변화를 줌으로써 UI 컨트롤러 인식하게 하는 일 뿐입니다. 정말 다리 역할만 하는 것이죠. loadUsers옆의 collect 구문은 trySend를 쏴준 데이터를 받아들이겠다는 것입니다. 람다형식으로 쏴준 데이터를 user라는 이름으로 받아 처리를 해주고 있는 것이지요. 여기서 주의할 점은 LiveData는 값 자체가 바뀌는 것만을 변화로 취급한다는 것입니다. 배열을 LiveData로 감싸주었지만 배열의 요소가 추가되고 제거되고 등의 변화는 LiveData는 감지하지 못한다는 것입니다. 배열의 값, 즉 감싸고 있는 배열의 주솟값 자체가(쉽게 말하면 새로운 배열이 들어와야) 변화를 감지한다는 것이지요. 아직 kotlinLiveData가 그 정도로 똑똑하지는 않으니 부디 저처럼 당연하게 믿고 썼다가 낭패를 보시지 않았으면 좋겠습니다. 그래서 저는 이와 같이 임시로 새로운 배열을 만들어 그곳에 들어오는 사용자 정보들을 다 저장한 뒤 postValueLiveData의 값을 바꿔준 것입니다.

여기서 또 아까 callbackFlow마냥 써있는 viewModelScope.launch는 무엇일까요. getUsers 안에 callback함수로 쓴 loadUsers가 있죠? 방금 설명들였듯이 load할 때까지 기다린다는 뜻입니다. 그런데 말입니다~ 기다리는 건 좋은데 그 때 마냥 멈춰있기에는 너무 비효율적이지 않나요? 그래서 우리는 이 친구를 뒤쪽에 둔 채 계속 나아가야할 코드들은 진행을 계속합니다. 다른 Thread 에서 진행되게끔 해서 Main Thread 에는 멈춤을 주지 않는 이 방식을 비동기 처리 라고 합니다. 굉장히 중요한 개념이니 이 또한 무조건 공부하셔야 하는 내용입니다. 저는 이렇게 쉽게 이해의 포문을 열어드렸고 마찬가지로 굉장히 많은 좋은 글들과 자료들로 관심있게 찾아봅시다. 어쨌든 결론적으로 이 비동기 처리를 kotlin에서는 coroutine 이라는 이름으로 제공을 하는 것이고 이 coroutineViewModel의 상태에 따라(ViewModel이 중지되면 coroutine도 중지되게끔 등등..) 진행을 하게끔 해주는 것이 viewModelScopelaunch메소드 랍니다.

이제 UI 컨트롤러로를 작성하여 봅시다. UI 컨트롤러 내에서 ViewModel과의 소통을 위해 ViewModel 변수를 생성하여 줍니다.

class MainActivity : AppCompatActivity() {
    private lateinit var viewModel: UserViewModel

        override fun onCreate(savedInstanceState: Bundle?) {
        ...
        viewModel = ViewModelProvider(this).get(UserViewModel::class.java)
        }
}

이제 UI 컨트롤러 내에서 ViewModelLiveData의 변화를 감지하기 위해 observe를 해줍시다.


override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    viewModel.contents.observe(this, Observer { users ->
        ...
    })
}

(추가적으로 Fragment라면 이 LiveDataFragment의 Lifecycle을 따라갈 수 있도록 this대신 viewLifecycleOwner을 넣어주셔야 합니다. ) 이렇게 LiveData안에 담긴 User 배열에 변화가 생겼을 때 이 값을 람다 형식을 통해 users로 받아 이를 이용해 UI를 다루는 코드를 작성하시면 된답니다.

어땠나요? 천천히 따라오니 이제 Android에서 MVVM을 어떻게 적용할 지 흐름을 좀 느끼셨나요? 그랬다면 다행입니다. 저 역시 처음 봤을 때 많이 헷갈렸던 새로운 개념인지라, 저와 같은 분에게 조금이나마 도움이 되고자 글을 써보았습니다. 그래서 인지 너무 쉽게 설명했기에 조금 더 큰 이해를 위해서는 각종 글들과 자료로 충분히 공부도 해보시고 무엇보다 적용해보시면서 MVVM을 완전정복 하셨으면 좋겠습니다!😄

profile
소프트웨어 엔지니어

0개의 댓글