본문 바로가기
Android/Kotlin

[Android] Compose MVI, MVVM+ (MvRx)

by DnaJ 2022. 8. 6.
반응형

Compose 와 잘 어울리는 MVI Architecture 하지만 저는 너무 어렵게 느껴졌습니다.

처음에 MVI Architecture 를 사용했을 때 Redux Style 의 MVI 를 사용했습니다.

RxJava 를사용 하여 구현했는데.... 결국 다른 사람들이 따라오지를 못하여 MVVM 으로 변경하는 사태가 벌어졌습니다.

저만 MVVM 으로 하자고 하고 나머지 분들은 다 MVI로 하자고 했는데 말이죠....? (아직까지 생각해봐도 조금 억울....)

어려워서 엎었던 기억이 있습니다.

 

 

https://play.google.com/store/apps/details?id=com.danchoo.tagalbum&hl=ko

 

태그앨범 - Google Play 앱

사진과 앨범을 태그로 관리하세요. 결혼식, 팬클럽, 동호회등 원하는 카테고리를 만들어 정리해보세요. 사진에 태그를 설정하여 손쉽게 찾아보세요!

play.google.com

 

 

하지만!!

MvRx 로 알려진 MVVM+ Style 의 MVI Architecture 는 Redux Style 보다 쉽고 간단했습니다.

 

그때 이걸로 할껄.... 후회되는 1인 입니다. 

개발을 할때 사전조사가 많이 필요한 이유를 다시 한번 깨닳게 되었습니다

반성....

 

 

MVI 

M : Model

V: View

I : Intent -> Android 의 Intent 가 아닙니다!!! Action 혹은 Command 입니다. 

 

MVI 는 단방향으로 이벤트가 이동하는 Architecture 입니다.

 

아래 그림을 보면 동그랗게 순환이 되어 이벤트가 흘러가게 됩니다.

User 가 Action or Event 을 발생시키면 (Intent 발생)

Model 에 상태가 변경 됩니다.

View는 Model의 상태에 따라 유저에게 해당 상태를 표현해줍니다.

 

https://smartstore.naver.com/happysiso

 

해피시소마켓 : 네이버쇼핑 스마트스토어

SISO

smartstore.naver.com

 

 

아이고 어렵다...

 

Redux Style
MVVM+ Style

 

 

Redux Style 의 MVI는 난이도가 높습니다...ㅎㅎ

하지만 MVVM+ Style 은 간단 합니다. 복잡하지 않습니다.

단순히 단방향으로 이벤트가 흘러간다고 생각만 하면 구현하기 쉽진 않지만...... 쉽다고 생각하면 편합니다.

 

 

Smaple 코드에서 Model, View, Intent 를 가지고 왔습니다.

하기 코드는 버튼을 클릭 했을 때 count가 증가 하는 것을 보여줍니다.

 

MVI 의 Model

    /**
     * MVI의 M : Model
     *
     * view의 상태이기 때문에 ViewState로 정의해 줬다.
     * 편의를 위해 default 값을 정해준다.
     */
    data class SampleViewState(
        val text: String = "Click Button",
        val count: Int = 0,
        val buttonText: String = "Button",
        val title: String = "Mvi Sample"
    )

 

 

MVI 의 View

/**
 * MVI의 V : View
 * Statefull composable
 */
@Composable
fun MainScreen(
    viewModel: SampleViewModel = viewModel()
) {

    val viewState = viewModel.viewState.value

    /**
     * LaunchedEffect key1을 Unit 으로 설정하여 한번만 생성이 되도록 한다.
     */
    LaunchedEffect(key1 = Unit) {
        /**
         * viewState 받아오기 위하여 subscribeToIntent를 호출해 준다.
         */
        viewModel.subscribeToIntent()

        /**
         * sideEffect 를 collect 하여 1회성 이벤트를 처리해 준다.
         */
        viewModel.sideEffect
            .onEach {
                when (it) {
                    is SampleSideEffect.CheckButtonClick -> {
                        Log.d("SideEffect", "CheckButtonClick")
                    }
                    is SampleSideEffect.ButtonClickComplete -> {
                        Log.d("SideEffect", it.message)
                    }
                }
            }.collect()
    }

    SampleScreenImpl(
        modifier = Modifier,
        viewState = viewState,
        onClickButton = {
            /**
             * SampleIntent.OnClickButton viewModel에 전달
             */
            viewModel.setIntent(SampleIntent.OnClickButton)
        }
    )
}

 

 

MVI 의 Intent

 /**
     * MVI의 I : Intent
     * ViewModel로 전달하는 Action or Command or Event
     */
    sealed class SampleIntent {
        object OnClickButton : SampleIntent()
    }

 

ViewModel 에서 Intent 처리

   fun subscribeToIntent() {
        viewModelScope.launch {
            _intent.collect { handleIntent(it) }
        }
    }

    /**
     * 전달 받은 Intent 를 처리
     */
    private suspend fun handleIntent(intent: SampleIntent) {
        when (intent) {
            is SampleIntent.OnClickButton -> {
                _sideEffect.send(SampleSideEffect.CheckButtonClick)

                /**
                 * _viewState.value 를 copy 하여 사용한다.
                 * copy 하여 사용 했을경우
                 * 변경하지 않는 데이터가 보존된다.
                 * 또한, Create 하는 것보단 Copy 하는것이 좋다고 한다.
                 */
                _viewState.value = _viewState.value.copy(
                    count = _viewState.value.count + 1
                )

                _sideEffect.send(SampleSideEffect.ButtonClickComplete("onClick button success"))
            }
        }
    }

    /**
     * view에서 전달 받은 Intent를 sharedFlow에 전달 해준다.
     */
    fun setIntent(intent: SampleIntent) {
        viewModelScope.launch {
            _intent.emit(intent)
        }
    }

전체 코드는 github에 있습니다.

https://github.com/danchoo21/mvi-sample

 

GitHub - danchoo21/mvi-sample

Contribute to danchoo21/mvi-sample development by creating an account on GitHub.

github.com

 

1. 유저가 버튼을 누릅니다 ( Intent 발생) 

2. ViewModel 에 Commnad or Action (Intent)을 보냅니다. (A Button 눌렀음 )

3. 해당  Commnad or Action (Intent) 을 수행 합니다. (Network 처리, DB R/W 기타 등등....) -> sample 에서는 count 증가

4. 수행한 해당 data 를 Model에 넣어줍니다. 

5. LiveData 혹은 Rx subscrive, Flow를 통하여 View에서 Observe 하고 있는 Model의 상태의 변경을 감지 합니다. 

6. View 를 업데이트 합니다.

 

 

 

참고 : https://appmattus.medium.com/top-android-mvi-libraries-in-2021-de1afe890f27

 

Top Android MVI libraries in 2021

Comparing redux and MVVM+ style MVI libraries

appmattus.medium.com

https://github.com/airbnb/mavericks

 

GitHub - airbnb/mavericks: Mavericks: Android on Autopilot

Mavericks: Android on Autopilot. Contribute to airbnb/mavericks development by creating an account on GitHub.

github.com

https://blog.mindorks.com/mvi-architecture-android-tutorial-for-beginners-step-by-step-guide

 

MVI Architecture - Android Tutorial for Beginners - Step By Step Guide

In this tutorial, first, we are going to learn about the MVI architecture in Android, and then we will build a project with MVI architecture. This tutorial is for beginners who want to get started with the MVI architecture.

blog.mindorks.com

 

 

반응형

댓글