안녕하세요:) 한끼족보의 Android 개발자 송혜음입니다
지난 글에서는 Compose에 대한 간단한 설명과 함께 Modifier 개념을 설명하는 아티클을 작성했는데요
이번에는 한끼족보 Android팀이 도입한 아키텍처에 대한 글을 작성해보고자 합니다
MVC MVP MVVM 등 너무나 많은 아키텍처들...여러분은 어떤 것이 가장 익숙하신가요?
사실 저는 MVVM 아키텍처를 주로 사용해와서 MVVM이 제일 익숙했었는데요
이번 한끼족보 Android팀은 새롭게 MVI 아키텍처를 사용했습니다
MVI가 익숙하지 않으신 분들 오늘 저와 함께 알아보도록 합시다
MVI는 무엇일까요?
MVI는 2018년도에 새롭게 등장한 아키텍처 패턴으로, Model View Intent 의 약자입니다
첫번째로 Model 입니다.
MVVM같은 다른 아키텍처에서는 Model을 데이터의 개념으로 사용하고 있습니다
하지만 MVI는 데이터 자체라기 보다는 앱의 상태 + 데이터를 나타냅니다
추후 MVI의 단방향 흐름에 대해 말씀드릴텐데 이러한 특성으로 인해서 Model은 불변 객체로 유지되어야 합니다
두번째로 View 입니다.
타 아키텍처와 동일하게 화면을 나타냅니다
세번째로 Intent 입니다. 다른 아키텍처에서는 본 적 없던 단어라 생소하시죠?
Intent라고 말씀드리면 아! Android Intent 말하는 거 아니야? 라고 생각하실 수 있는데 단어 의미 그대로 '의도'라고 생각하시면 됩니다, Intent는 사용자의 의도 즉 '앱의 상태를 바꾸는 요청'이라고 이해해주세요!
MVI는 즉 상태 관리를 중요하게 여기는데요
엇 .. 상태..? Compose도 상태를 중요하게 여기지 않나?
정답입니다
Compose는 지난 아티클에서 언급했듯이 선언형 UI로 상태가 바뀔때마다 Recomposition이 일어나게 되므로 상태관리를 통해 불필요한 Recomposition을 줄여야합니다
MVI와 Compose 모두 상태관리가 핵심이므로 둘은 아마 환상의 짝꿍이 아닐까.. 감히 말해봅니다
MVI는 단방향 흐름이라는 특징을 가지고 있습니다
View > 사용자 >Intent > Model > View > 사용자 ... 이렇게 하나의 사이클이 연속됩니다
사용자가 화면을 보고 특정 이벤트를 발생시키면 상태(모델)가 바뀌면서 상태에 맞는 새로운 화면이 업데이트 되고..의 무한반복 입니다
단방향 흐름의 장점에는 무엇이 있을까요?
바로 데이터의 흐름을 예측할 수 있다는 것입니다, 데이터 흐름이 정해져있으므로 어느곳에서 문제가 발생했는지 확인할 수 있습니다. 더불어 한 방향으로만 진행되니 Model(상태) 간 충돌이 일어나기 힘들겠죠?
Model은 이런 흐름 속에 존재하며 외부 요소로부터 상태가 변경되지 않기 위해 불변객체로 정의됩니다
MVI는 순수함수로 이루어진 pure cycle라고도 불리는데요
순수함수란 y=x 의 함수꼴처럼 인자에 x를 넣으면 항상 y의 값을 갖는(동일한 인자를 넣으면 항상 같은 값을 갖는) 함수를 말합니다
view(model(intent()))
사실 어그로였습니다
MVI는 Pure Cycle 말고도 부수효과로 이루어진 Side Effect Cycle 두 종류로 구분됩니다
부수효과란 외부의 상태를 변경하거나 함수로 들어온 인자의 상태를 직접 변화시키는 효과를 말합니다
Intent와 Model 사이에 Side Effect 가 존재합니다
이 친구의 목적은 무엇일까요? 이름처럼 Side Effect를 처리합니다
Intent처럼 Model > View 사이클로 이동거나 / Side Effect 에서 사이클이 종료되거나 / 다른 Intent를 호출할 수도 있습니다
State와 Side Effect를 구분하는 방법은 상태 변경이 필요한지 필요하지 않은지의 차이로 이해하시면 될 것 같습니다
Side Effect의 가장 대표적인 예로는 토스트 메세지가 있는데요, 토스트 메세지의 경우는 화면에 특정 메세지를 띄우고 사라지기에 Side Effect에서 해당 사이클이 종료됩니다
이제 한끼족보의 코드를 통해서 MVI 아키텍처를 자세히 알아볼까요?
다음은 MyStore 패키지의 코드입니다
1.Model(State)
UiState 활용을 통해 화면의 State를 정의했습니다
data class MyStoreState (
val uiState : EmptyUiState<PersistentList<MyStoreModel>> = EmptyUiState.Loading
)
2.Side Effect
sealed class를 통해 해당 화면에서의 Side Effect를 정의합니다
이 화면에서는 화면 이동 외에는 따로 효과가 없으므로 navigate 하나만 정의했습니다
화면 이동은 화면 내부를 변화시키는 요소가 아니기 때문에 Side Effect로 분류됩니다
sealed class MyStoreSideEffect {
data class NavigateToDetail(val id: Long) : MyStoreSideEffect()
}
State는 stateflow / Side effect는 sharedflow를 사용해 flow를 관리합니다
private val _myStoreState = MutableStateFlow(MyStoreState())
val myStoreState: StateFlow<MyStoreState>
get() = _myStoreState.asStateFlow()
private val _mySideEffect: MutableSharedFlow<MyStoreSideEffect> = MutableSharedFlow()
val mySideEffect: SharedFlow<MyStoreSideEffect>
get() = _mySideEffect.asSharedFlow()
State 관리의 경우,
상태가 바뀔 때마다 state를 만들어주면 메모리를 그만큼 잡아먹게 되는 것이므로 copy를 사용해 객체의 값을 새롭게 업데이트합니다
.onSuccess {
val currentState = _myStoreState.value.uiState
if (currentState is EmptyUiState.Success) {
val updatedItems = currentState.data.map {
if (it.id == id) {
it.copy(isLiked = true)
} else {
it
}
}.toPersistentList()
//새로운 값으로 state의 값을 업데이트합니다.
_myStoreState.value = _myStoreState.value.copy(
uiState = EmptyUiState.Success(updatedItems)
)
}
}
Side Effect 관리의 경우,
해당 값을 화면이 계속 가지고 있어야 할 이유가 없기때문에 emit을 통해 방출시킵니다
이렇게 emit된 Side Effect는 view 내에서 collect를 통해 감지하고 해당하는 로직이 실행됩니다
fun navigateToStoreDetail(storeId: Long) {
viewModelScope.launch {
// side effect를 방출합니다.
_mySideEffect.emit(MyStoreSideEffect.NavigateToDetail(storeId))
}
}
LaunchedEffect(myStoreViewModel.mySideEffect, lifecycleOwner) {
myStoreViewModel.mySideEffect.flowWithLifecycle(lifecycleOwner.lifecycle)
.collect { sideEffect ->
when (sideEffect) {
//side effect의 값에 따라 다른 로직을 실행합니다.
is MyStoreSideEffect.NavigateToDetail -> navigateToDetail(sideEffect.id)
}
}
}
자세한 코드는 한끼족보 Android 팀의 깃허브를 통해서 확인해주세요!
https://github.com/Team-Hankki/hankki-android
MVI 패턴은 틴더,에어비앤비 등 다양한 기업에서 이미 사용 중에 있다고 합니다
매번 느끼지만 프론트엔드는 정말 새롭게 등장하는 기술들이 많은 것 같아요!
앞으로도 새롭게 나오는 내용들을 열심히 따라가야겠다는 생각이 드네요
(언제쯤 먼저 치고나갈 수 있을까)
오늘도 읽어주셔서 감사합니다
아직 공부중이기 때문에 잘못된 부분이 있다면 자유롭게 알려주시면 감사하겠습니다
다음에 더 발전된 글로 돌아오겠습니다,좋은 하루 되세요 :)
'Android' 카테고리의 다른 글
저는 Developer가 아닙니다 (1) | 2024.08.24 |
---|---|
힐트, 클린아키텍처 그리고 로그인 리이슈... (1) | 2024.08.07 |
UI도 두들겨 보고 건너라 - modifier 알아보기 (1) | 2024.08.07 |
한끼족보 Android 팀이 도입한 기술과 근거 모음.zip (2) | 2024.08.05 |