새로운 프로젝트를 들어가기전에 MVI를 공부하기 위해 해당 글을 작성하게 되었습니다. 새로운 프로젝트는 Compose를 사용해 진행할 예정입니다. 제 스스로 생각해도 아직 기존 방식의 안드로이드 개발도 정말 많이 부족하지만 Compose를 사용해야겠단 생각이든 몇번의 계기가 있습니다.
첫번째로 컨퍼런스였는데요, 이제는 안드로이드 컨퍼런스에서 컴포즈는 빼놓을 수 없는 주제인 것 같습니다. 하지만 저는 컴포즈에 대한 지식이 전무했고 선배 개발자분들의 소중한 발표들을 이해하지 못하는 것이 너무 아깝다고 느껴졌습니다.
두번째로, 이전 프로젝트에서 겪은 문제점들 때문입니다. 컴포즈를 사용할 때 굳이 MVI를 사용하지 않아도 구현 할 수 있습니다. 아니, MVI를 사용하지 않고 태초의 방식으로 컴포즈 개발을 한 후 직접 문제들을 겪고 이 문제들을 해결하며 MVI로 마이그레이션 하는 것이 가장 좋은 성장 방법이겠죠. 그럼에도 불구하고 MVI를 사용하는 이유는 제가 직접 느낀 MVI의 장점 때문입니다. 저는 Hilt를 "찰스의 안드로이드" 블로그를 운영중인 찰스님의 강의를 통해 배웠고 이 강의는 컴포즈와 MVI, Orbit(이하 MVI 삼총사)을 사용해 진행되었습니다. 당시 저는 컴포즈에 대한 경험과 지식이 제로였음에도 불구하고 코드의 흐름을 모두 이해할 수 있었습니다. MVI 삼총사가 모두 처음인 사람이 이런 이해를 가능하게 한 것은 MVI, 정확히는 Orbit을 사용한 MVI의 가독성 때문이었고 개인적으로 이 삼총사를 사용한 코드의 가독성이 진짜 미쳤다고 생각했습니다.
이전 프로젝트에서 CleanArchitecture를 사용하면서 Data에서 Domain Layer까지는 비즈니스 로직의 플로우가 코드만 봐도 이해가 될 정도로 잘 만들었다고 생각했는데 문제는 Presentation Layer였습니다. Presentation Layer를 MVVM 아키텍처에 입각하여 뷰와 로직을 분리하다보니 갈수록 ViewModel은 뚱뚱해지고 가독성은 점점 최악이 되었습니다. ViewModel을 다이어트 시키고 싶어 Anemic Domain Model을 최대한 지양하려 했지만 검색 옵션 선택 등 사용자의 액션으로 일어난 이벤트가 메인인 프로젝트 특성상 ViewModel을 다이어트하는 것은 불가능했습니다. 하다못해 가독성이라도 올려보자는 마음에 상태 관리 클래스를 만들었지만 ViewModel은 여전히 제 몸무게 만큼 뚱뚱했죠.
그래서 이런 문제들을 해결해보자 이번 프로젝트에서 MVI 삼총사를 사용하게 되었습니다.
1. MVI란 무엇일까 ?
MVI는 3가지 요소의 앞 글자를 따서 만든 패턴으로 상태 기반의 UI 업데이트를 다루는 패턴입니다.
- View :Activity, Fragment, Composable 등 UI 컴포넌트로 표현됩니다.
- Intent : 사용자의 액션(예: 버튼 클릭, 스크롤) 또는 시스템 이벤트에서 발생한 이벤트를 나타내며 이를 기반으로 Model을 변경시키는 트리거 역할을 합니다.
- Model: UI의 상태(state)를 나타냅니다. 사용자의 Intent에 따라 상태가 변경되고, 변경된 상태가 다시 View에 반영됩니다. Model은 불변(immutable) 상태로 처리되며 이는 상태 관리의 일관성을 유지하는 데 중요한 역할을 합니다.
2. MVI는 어떻게 동작할까 ?
앞서 살펴본 MVI의 구성 요소를 바탕으로 예를 들어봅시다. 유저가 관광지 목록에서 북마크 버튼을 클릭했을 때 유저는 어떠한 의도(Intent)를 가지고 이 액션을 취한 것일까요 ?
해당 관광지를 북마크 목록에 추가한다.
사용자가 버튼 클릭이라는 액션은 관광지를 북마크 목록에 추가한다는 새로운 의도(Intent)를 발생시키고 이를 통해 새로운 상태(Model)를 만들고 View는 이 새로운 Model을 반영해 갱신됩니다. 이렇듯 MVI는 단방향으로만 데이터를 흐르게 하는 UDF 형태입니다. 그리고 공식문서에서 제시하는 컴포즈의 아키텍처 또한 UDF를 지향합니다.
Compose에서의 상태 관리는 MVI와 비슷한 구조로 동작합니다. Compose에서는 상태가 UI를 정의하며, 상태(State)가 변경될 때마다 Compose는 자동으로 View를 다시 그립니다(recomposition). 이 방식은 MVI에서의 단방향 데이터 흐름과 일치하며, 사용자 이벤트(예: 버튼 클릭)로 인해 Intent가 발생하고, 이로 인해 상태가 변경되며 UI가 다시 그려지는 흐름을 갖고 있습니다.
따라서, MVI와 Compose의 아키텍처 모두 단방향 데이터 흐름을 지향하며 이는 상태 관리와 UI 업데이트를 예측 가능하게 하고 유지보수를 용이하게 만듭니다.
SideEffect
앞서 살펴본 것 처럼 모든 동작이 유저의 의도에 의해 순수함수 구조로 순환하길 기대 하지만 그렇지 못한 경우가 있습니다.
순수 함수 : 동일한 입력에 대해 항상 동일한 출력을 내는 함수로, 외부 상태에 의존하지 않고 부작용이 없는 것
예를 들어 토스트, 스낵바, Activity/Fragment 이동 같이 상태 변경이 필요 없는 이벤트가 필요할 수 있습니다.이러한 동작은 상태(State)를 변경하지 않으며, UI 상에서 특정 순간에만 발생하는 일회성 이벤트입니다. MVI에선 이러한 이벤트를 처리하기 위해 SideEffect라는 개념을 사용합니다.
예를 들어 유저가 로그인 버튼을 클릭하면 이는 Intent로 해석되고 서버에서 로그인 처리가 완료되면 유저가 메인 화면으로 이동해야 합니다. 이 동작은 로그인 상태 자체를 변경하는 것은 아니지만, UI 상에서 화면 전환이 필요하므로 SideEffect로 처리합니다.
'Android Architecture' 카테고리의 다른 글
MVVM에서 MVI로 (3) | 2024.11.07 |
---|---|
클린 아키텍처가 의존성 역전 원칙을 활용하는 원리 (0) | 2024.05.23 |
클린아키텍처를 지향하는 아키텍처 (0) | 2024.05.15 |
객체지향의 꽃 다형성과 의존성 주입 (0) | 2024.05.06 |
FireBase 의존성 주입 생각해보기 (0) | 2024.05.01 |