[Android][MVI] 3. MVI의 SideEffect Cycle

Choi Sang Rok·2022년 5월 9일
4

MVI

목록 보기
3/3

https://velog.io/@evergreen_tree/AndroidMVI-2.-MVI%EC%9D%98-Purecycle 에서 이어집니다.


🚶‍♂️SideEffect


항상 모든 흐름이 Pure cycle로 이루어 질 순 없습니다.

앱은 외부세계의 상태를 변화시켜야 하는 상황이 발생하기도 하고, 외부 세계로부터 상태가 변화될 수 있습니다.

예를들어, Composable 함수 같은 경우, compose 내의 State가 변경되면 이를 통해 재 호출 되어 Recomposition 되는 형태로만 존재해야 합니다.

@Composable
fun ExampleButton{
    SideEffect{
	    systemUiController.setStatusBarColor(
        	color = BackgroundColor
	    )
	}
}

하지만 위 코드처럼, systemUiController의 상태를 변경시켜야 하는, 즉 SideEffect를 발생시켜야 하는 상황에 직면하기도 합니다.

SideEffect()는, Composition이 성공적으로 완료되었을 때 부수 효과를 예약하는 함수로 Compose에서 부수 효과를 처리하기 위한 방법 중 하나입니다.

Compose 얘기는 여기하지 하고, 안드로이드의 Orbit FrameWork 이용하여 MVI의 SideEffect Cycle을 구현해 보겠습니다.



👨‍💼Side effect Cycle


SideEffect Cycle은 기존 Pure Cycle에 SideEffect를 추가한 것입니다.
intent()를 사용하여 새로운 상태를 생성함동시에 부수 효과를 실행하고, 인텐트로 결과를 내보내거나 아무 일도 일어나지 않습니다.

버튼을 클릭했을 때 토스트 메시지를 출력하는 간단한 앱을 MVI + Compose로 만들어 보겠습니다.

SideEffect 정의

data class CalculatorState (
    val total: Int = 0
)

sealed class CalculatorSideEffect{
    data class Toast(val text: String): CalculatorSideEffect()
}
  • SideEffect에 대한 클래스를 생성합니다.

함수형 프로그래밍인 자바스크립트에서도, Side Effect는 존재합니다. 함수가 일관된 결과를 보장하지 못하거나, 함수 외부 어디든지 조금이라도 영향을 주는 경우에 발생하게 됩니다.


SideEffect Post

class CalculatorViewModel: ViewModel(), ContainerHost<CalculatorState, CalculatorSideEffect> {

    override val container =container<CalculatorState,CalculatorSideEffect>(CalculatorState())

    fun add(number :Int) = intent{//intent - 앱의 상태를 변화시키려는 의도
        postSideEffect(CalculatorSideEffect.Toast("Adding $number to ${state.total}!"))
        reduce{
 				state.copy(total = state.total + number)
		}
    }
}
  • ViewModel 에서 State와 SideEffect를 가지고 container를 재정의합니다.

  • add라는 메소드를 intent에 담고 Toast라는 SideEffect에 Toast에 들어갈 메시지를 담아 SideEffect를 보냅니다.

  • 연산된 결과를 reduce를 통해 새로운 state를 만들어 내는 과정은 Pure Cycle과 같습니다. 단지 postSideEffect가 추가 되었을 뿐입니다.


Launch SideEffect

@Composable
fun TestPage(
    viewModel: CalculatorViewModel,
)

...

LaunchedEffect(viewModel){
viewModel.container.sideEffectFlow.collect{
when(it){
            is CalculatorSideEffect.Toast -> Toast.makeText(context,it.text, Toast.LENGTH_SHORT).show()
        }
	}
}
...
  • Composable 함수에서 LaunchedEffect 블럭 안에서, SideEffect를 수집하고, Toast를 실행하면 끝입니다.


✍MVI의 장단점


장점

  • 앱의 상태가 하나뿐이기 때문에, 상태 충돌이 없음
  • 데이터의 흐름이 정해져 있어 흐름을 이해하고 관리하기 쉬움
  • 각각 값이 불변하기 때문에, 스레드 안정성을 갖는다.

단점

  • 러닝커브가 높음 (필자도 이해하느라 어려웠습니다.)
  • 위의 앱에서 봤듯이, 간단한 앱이라도 Intent와 Model을 가져야 함
  • 작은 변경도 Intent를 거쳐야함
  • Model Update를 위해 매번 새로운 인스턴스를 생성해야 해서, 너무 많은 객체가 만들어지면 GC가 빈번하게 작동될 수 있음

🙋‍♂️이상으로 MVI 시리즈를 마치겠습니다. 감사합니다.


참고 자료
https://sungbin.land/아직도-mvvm-이젠-mvi-시대-319990c7d60
https://medium.com/@kimdohun0104/mvi-패턴에-대한-고찰-이유와-방법-그리고-한계-767cc9973c98
https://dev.to/kaleidot725/implementaing-jetpack-compose-orbit-mvi-3gea

profile
android_developer

2개의 댓글

comment-user-thumbnail
2022년 5월 10일

아싸 1빠

답글 달기
comment-user-thumbnail
2022년 5월 16일

졌네요,,, 제가 2빠가 되는 날이 오다니 너무 힘든 하루입니다... 다음엔 꼭 seunghyun1217 님을 이기도록 하겠습니다 :-(

답글 달기