Implementing Jetpack Compose + Orbit MVI
Tags: android, kotlin, jetpackcompose, orbitmvi
Published: 2022-02-10 18:07:29

I created a Pokémon Libary application with Jetpack Compose + Orbit MVI. Orbit MVI is easy to use, try it.

![Image.png](https://res.craft.do/user/full/3a21bd0e-fe7a-39aa-73ad-b52ef24b655b/doc/F6345393-103D-49B0-9A99-865E3AFA5868/BD2499F1-A7A3-4E37-96FF-97F6D53ECC27_2/yTIJGlQS4Dc52wQx4yfuAacPpIzaFV8JrB1ERKLRfzsz/Image.png)

# Link

This Sample Source Code

- 
 https://github.com/kaleidot725/PokemonLibraryWithJetpackCompose

About Jetpack Compose

- https://developer.android.com/jetpack/compose

About Orbit MVI

- https://orbit-mvi.org

# Feature

- Manage Pokémon data.
- Display multiple Pokémon in a list.
- Display a Pokémon details.

# Architecture

This application architecture is based on the MVVM + Repository pattern. But the view state management use MVI, because introduced Jetpack Compose and Orbit MVI.

![architecture-OVERALL.drawio.png](https://res.craft.do/user/full/3a21bd0e-fe7a-39aa-73ad-b52ef24b655b/doc/F6345393-103D-49B0-9A99-865E3AFA5868/8AD1BC53-4EB5-416F-91C3-095BF50160F9_2/Qhd8zDcHO337yOkh1Jq8mDJOiZK6k7ZMzmNrAAxzEjYz/architecture-OVERALL.drawio.png)

# Module

This application consists of a multi-module structure. There are three modules, and each module plays the role described in the figure below.

| Name     | Content                                                   |
| ------ | ---------------------------------------------------- |
| App    | Store View and ViewModel to manage view state.       |
| Domain | Store UseCase to process business logic.             |
| Data   | Store Repository and Dao to manage Pokémon database. |

# Library

This application is created by the  library in the bottom figures.

| **Name**              | **Link**                                                                                                                         |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Koin                  | [https://insert-koin.io](https://insert-koin.io/)                                                                                |
| Jetpack Compose       | [https://developer.android.com/jetpack/compose](https://developer.android.com/jetpack/compose)                                   |
| Navigation Compose    | [https://developer.android.com/jetpack/compose/navigation](https://developer.android.com/jetpack/compose/navigation)             |
| Orbit MVI             | [https://orbit-mvi.org](https://orbit-mvi.org/)                                                                                  |
| Coil                  | [https://coil-kt.github.io/coil/](https://coil-kt.github.io/coil/)                                                               |
| Room                  | [https://developer.android.com/training/data-storage/room?hl=ja](https://developer.android.com/training/data-storage/room?hl=ja) |
| Kotlin-Serialiazation | [https://github.com/Kotlin/kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization)                               |
| PokemonGO-Pokedex     | [https://github.com/Biuni/PokemonGO-Pokedex](https://github.com/Biuni/PokemonGO-Pokedex)                                         |

# Data Flow for UI (UI State Managment)

This application data flow is undirectional,  as shown in the bottom figures.

1. MVI View sends Action(intent) to MVI Model.
2. MVI Model executes UseCase when received Action
3. UseCase accesses to Repository to get Pokémon Data.
4. UseCase returns  Pokémon Data to MVI Model.
5. MVI Model creates new state from Pokémon Data.
6. MVI Model sends created new states or new events to MVI View.

![architecture-DATAFLOW.drawio.png](https://res.craft.do/user/full/3a21bd0e-fe7a-39aa-73ad-b52ef24b655b/doc/F6345393-103D-49B0-9A99-865E3AFA5868/C46C8974-BE1B-4C0C-B4D5-E2C29F62D5DC_2/t4SQ2YUUDuU3SE13IGo1MagaA9eT9SqkIT0IxSSjdpMz/architecture-DATAFLOW.drawio.png)

This application data flow is implmented by Orbit MVI. (For more information on how to create an MVI View or MVI Model using Orbit MVI, please check [here](https://orbit-mvi.org).)

### MVI Model

```kotlin
data class InitState(
    val status: UiStatus? = null
)

sealed class InitSideEffect {
    object Completed : InitSideEffect()
}

class InitViewModel(
    private val fetchAllPokemonUseCase: FetchAllPokemonUseCase
) : ContainerHost<InitState, InitSideEffect>, ViewModel() {
    override val container = container<InitState, InitSideEffect>(
        InitState()
    )

    init {
        fetchData()
    }

    fun retry() {
        if (container.stateFlow.value.status != UiStatus.Loading) {
            fetchData()
        }
    }

    private fun fetchData() {
        intent {
            reduce { state.copy(status = UiStatus.Loading) }
            if (fetchAllPokemonUseCase()) {
                reduce { state.copy(status = UiStatus.Success) }
                postSideEffect(InitSideEffect.Completed)
            } else {
                reduce { state.copy(status = UiStatus.Failed()) }
            }
        }
    }
}
```

## MVI View

```kotlin
@Composable
fun InitPage(
    viewModel: InitViewModel,
    onCompleted: () -> Unit
) {
    val state by viewModel.container.stateFlow.collectAsState()

    LaunchedEffect(viewModel) {
        viewModel.container.sideEffectFlow.collect {
            when (it) {
                is InitSideEffect.Completed -> onCompleted()
            }
        }
    }

    Scaffold {
        Box(modifier = Modifier.fillMaxSize()) {
            when (state.status) {
                UiStatus.Loading -> {
                    DownloadingMessage(
                        modifier = Modifier
                            .wrapContentSize()
                            .align(Alignment.Center)
                    )
                }
                is UiStatus.Failed -> {
                    DownloadRetryMessage(
                        onRetry = { viewModel.retry() },
                        modifier = Modifier
                            .wrapContentSize()
                            .align(Alignment.Center)
                    )
                }
                UiStatus.Success -> Unit
            }
        }
    }
}
```

# References

- [Koin | Jetpack Compose](https://insert-koin.io/docs/reference/koin-android/compose)
- [Navigation Compose | Navigating With Compose](https://developer.android.com/jetpack/compose/navigation)
- [Orbit MVI | Android ViewModel module](https://orbit-mvi.org/Android-ViewModel/overview)
- [MVI Architecture - Android Tutorial for Beginners - Step By Step Guide](https://blog.mindorks.com/mvi-architecture-android-tutorial-for-beginners-step-by-step-guide)
- [Coil | Jetpack Compose](https://coil-kt.github.io/coil/compose/)