MVI 패턴이란
Android MVI 패턴은 Model-View-Intent의 약자로, 안드로이드 앱 개발에서 사용되는 아키텍처 패턴 중 하나입니다.
MVI 패턴은 기존의 MVP, MVVM 등 다른 패턴과는 다르게 데이터 흐름이 단방향으로만 흐르는 것이 특징입니다. 뷰에서 인텐트를 발생시켜 모델에 전달하고, 모델에서 처리한 결과를 다시 뷰로 전달하는 방식으로 동작합니다.
MVI 패턴에서는 상태를 중심으로 구성됩니다. 앱의 상태를 변경하는 인텐트는 불변의 데이터 구조로 구현되며, 상태 변화를 처리하는 모델 역시 불변의 데이터 구조로 구현됩니다. 뷰는 상태를 표시하기 위해 불변의 데이터 구조를 이용합니다.
이러한 구조는 다양한 이점을 가지고 있습니다. 불변의 데이터 구조를 사용하므로 코드의 안정성이 높아지고, 뷰와 모델 사이의 인터페이스가 단순해지며, 테스트도 쉽게 구성할 수 있습니다. 또한, 상태가 변화할 때마다 전체 뷰를 다시 그리는 것이 아니라 필요한 부분만 업데이트하므로 성능도 개선됩니다.
하지만 MVI 패턴은 기존의 MVP, MVVM 등 다른 패턴보다 러닝 커브가 높고, 구현이 복잡합니다. 따라서, 앱의 규모와 복잡도가 크지 않다면 다른 패턴을 선택하는 것이 좋을 수 있습니다.
MVI 패턴의 등장 이유
MVI 패턴은 안드로이드 앱 개발에서 생기는 문제를 해결하기 위해 등장하였습니다.
안드로이드 앱 개발에서는 뷰와 모델 사이의 의존성이 강하게 작용하는 문제가 있습니다. 이로 인해 유지보수가 어렵고, 코드의 재사용성이 떨어집니다. 또한, 앱의 규모가 커지면서 발생하는 문제도 있습니다. 상태 변화에 따른 뷰의 업데이트와 비즈니스 로직의 처리를 동시에 처리하면서, 코드가 복잡해지고 유지보수가 어려워집니다.
이러한 문제를 해결하기 위해 MVI 패턴은 상태를 중심으로 구성됩니다. 불변의 데이터 구조를 이용하여 상태를 변경하고, 뷰는 이 상태를 표시합니다. 인텐트를 이용하여 상태 변화를 처리하는 로직은 모델에서 처리하며, 모델에서 처리한 결과는 다시 불변의 데이터 구조로 뷰에 전달됩니다.
이러한 구조는 뷰와 모델 사이의 의존성을 낮추면서도 뷰와 모델 간의 상호작용을 원활하게 처리할 수 있도록 합니다. 또한, 상태가 변화할 때마다 전체 뷰를 다시 그리는 것이 아니라 필요한 부분만 업데이트하므로 성능도 개선됩니다.
Model-View-Intent
- Model: 앱의 비즈니스 로직을 처리합니다. 상태를 변경하고, 데이터를 처리합니다. MVI에서는 불변의 데이터 구조를 이용하여 상태를 처리합니다. 모델은 상태 변화를 처리하고, 이를 불변의 데이터 구조로 뷰에 전달합니다.
- View: 앱의 UI를 처리합니다. 뷰는 상태를 표시하기 위해 불변의 데이터 구조를 이용합니다. 뷰는 인텐트를 발생시켜 모델에 전달하고, 모델에서 처리한 결과를 다시 불변의 데이터 구조로 받아 화면에 표시합니다.
- Intent: 뷰에서 발생한 사용자의 액션을 모델에 전달합니다. 인텐트는 불변의 데이터 구조로 구현됩니다. 뷰에서 인텐트를 발생시켜 모델에 전달하고, 모델에서는 이 인텐트를 처리하여 상태를 변경합니다.
MVI 패턴에서는 상태가 중심이 되며, 데이터는 불변의 구조로 구성됩니다. 불변의 데이터 구조를 사용하면 코드의 안정성이 높아지고, 테스트도 쉽게 구성할 수 있습니다. 상태가 변화할 때마다 전체 뷰를 다시 그리는 것이 아니라 필요한 부분만 업데이트하므로 성능도 개선됩니다.
Orbit
Orbit 라이브러리는 MVI 패턴에서 Model과 Intent를 처리하는 데 중점을 두고 있습니다. Orbit는 Android Architecture Components의 ViewModel과 LiveData를 기반으로 하며, 불변의 데이터 구조를 사용하여 상태를 처리합니다.
Orbit는 다음과 같은 기능을 제공합니다.
- 중앙 집중식 스토어: 모든 상태를 중앙 집중식으로 저장하고, 관리합니다. 이를 통해 상태를 안정적으로 유지할 수 있습니다.
- 불변성 보장: 모든 데이터는 불변의 데이터 구조로 구현됩니다. 이를 통해 안정성을 높이고, 오류를 방지할 수 있습니다.
- 디버깅 기능: 디버깅 기능을 제공하여, 상태의 변경 이력을 추적하고 분석할 수 있습니다.
Orbit는 Kotlin으로 작성되었으며, Android Studio 3.5 이상에서 사용할 수 있습니다. Orbit의 사용 방법과 예제는 공식 문서에서 확인할 수 있습니다.
MVI 패턴의 장점
- 안정성: MVI 패턴은 불변의 데이터 구조를 사용하여 안정성을 보장합니다. 상태를 변경할 때마다 새로운 객체를 생성하기 때문에, 예기치 않은 상태 변경을 방지할 수 있습니다. 이를 통해 코드의 안정성이 높아지고, 디버깅이 쉬워집니다.
- 테스트 용이성: MVI 패턴은 뷰와 모델을 분리하여 테스트하기 쉬워집니다. 모델은 단위 테스트를 실행하기 적합한 코드이며, 뷰는 UI 테스트를 실행하기 적합한 코드입니다. 이를 통해 코드의 품질이 높아집니다.
- 성능 개선: MVI 패턴은 상태가 중심이 되므로, 불필요한 업데이트를 방지할 수 있습니다. 상태가 변경될 때마다 전체 뷰를 다시 그리는 것이 아니라, 필요한 부분만 업데이트하므로 성능이 개선됩니다. 또한 중앙 집중식 스토어를 사용하여 모든 상태를 관리하므로, 상태의 일관성을 유지할 수 있습니다.
MVI 패턴은 안정성, 테스트 용이성, 성능 개선 등 다양한 장점을 가지고 있습니다. 이를 통해 코드의 품질이 높아지고, 유지 보수가 쉬워지며, 사용자 경험이 개선될 수 있습니다.
MVI패턴의 단점
- 학습 곡선: MVI 패턴은 다른 아키텍처 패턴에 비해 학습 곡선이 높을 수 있습니다. MVI 패턴을 처음 사용하는 개발자들은, 상태, 뷰, 인텐트, 리듀서 등 다양한 개념을 이해하고 적용해야 하기 때문입니다.
- 복잡성: MVI 패턴은 모델-뷰-컨트롤러(MVC)나 모델-뷰-프레젠터(MVP)에 비해 복잡할 수 있습니다. MVI 패턴은 상태를 중심으로 구성되어 있으므로, 상태를 관리하기 위해 많은 코드를 작성해야 합니다.
- 코드 양 증가: MVI 패턴은 상태를 중심으로 구성되어 있으므로, 상태 변경을 처리하기 위해 추가적인 코드를 작성해야 합니다. 이로 인해 코드 양이 증가할 수 있으며, 개발자들은 이를 관리하기 위해 더 많은 노력을 기울여야 합니다.
MVI 패턴은 안정성, 테스트 용이성, 성능 개선 등 다양한 장점을 가지고 있지만, 학습 곡선이 높고 복잡성이 높으며, 코드 양이 증가할 수 있습니다. 따라서 개발자들은 프로젝트의 특성과 요구 사항에 따라 적절한 아키텍처 패턴을 선택해야 합니다.
예시 코드
// View 인터페이스
interface MainView {
fun render(state: MainViewState)
}
// 상태 클래스
data class MainViewState(val count: Int)
// 인텐트 클래스
sealed class MainViewIntent {
object IncrementCountIntent: MainViewIntent()
object DecrementCountIntent: MainViewIntent()
}
// 리듀서 클래스
class MainViewReducer: Reducer<MainViewState, MainViewIntent> {
override fun reduce(previousState: MainViewState, intent: MainViewIntent): MainViewState {
return when (intent) {
is MainViewIntent.IncrementCountIntent -> previousState.copy(count = previousState.count + 1)
is MainViewIntent.DecrementCountIntent -> previousState.copy(count = previousState.count - 1)
}
}
}
// 모델 클래스
class MainViewModel: ContainerHost<MainViewState, MainViewIntent>(), Container<MainViewState> {
private val reducer = MainViewReducer()
override val container = this
override val initialState = MainViewState(0)
override fun onCreate() {
// 초기 상태 설정
publish(initialState)
}
override fun onIntent(intent: MainViewIntent) {
// 인텐트 처리
val newState = reducer.reduce(state, intent)
publish(newState)
}
}
// 액티비티 클래스
class MainActivity : AppCompatActivity(), MainView {
private val viewModel = MainViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 구독 설정
viewModel.container.stateFlow
.onEach { state -> render(state) }
.launchIn(lifecycleScope)
// 버튼 클릭 이벤트 처리
incrementButton.setOnClickListener { viewModel.send(MainViewIntent.IncrementCountIntent) }
decrementButton.setOnClickListener { viewModel.send(MainViewIntent.DecrementCountIntent) }
}
override fun render(state: MainViewState) {
// 뷰 업데이트
countTextView.text = state.count.toString()
}
}
위 코드에서는 MainView 인터페이스를 구현하여 뷰를 나타내고, MainViewState 클래스를 사용하여 상태를 나타냅니다. MainViewIntent 클래스는 사용자의 액션을 나타내고, MainViewReducer 클래스는 인텐트를 처리하여 상태를 변경합니다. MainViewModel 클래스는 Container와 ContainerHost 인터페이스를 구현하여 상태를 관리하고, 인텐트를 처리합니다. 마지막으로 MainActivity 클래스에서는 뷰와 뷰 모델을 연결하고, 액션을 처리합니다.
'Android > Study' 카테고리의 다른 글
[Android] Glide 간단 정리 (0) | 2023.07.28 |
---|---|
[Android] Retrofit (0) | 2023.05.06 |
[Android] 의존성 주입(Dependency Injection) (0) | 2023.04.27 |
[Android] Coroutine(코루틴) 간단 정리 (0) | 2023.04.27 |
[Android] 모듈을 기반으로하는 클린 아키텍처를 셋팅 (0) | 2023.02.22 |