DEV Community


Posted on

Implementing Jetpack Compose + Orbit MVI

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



This Sample Source Code

About Jetpack Compose

About Orbit MVI


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


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



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.


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

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.


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.)

MVI Model

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>(

    init {

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

    private fun fetchData() {
        intent {
            reduce { state.copy(status = UiStatus.Loading) }
            if (fetchAllPokemonUseCase()) {
                reduce { state.copy(status = UiStatus.Success) }
            } else {
                reduce { state.copy(status = UiStatus.Failed()) }
Enter fullscreen mode Exit fullscreen mode

MVI View

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 -> {
                        modifier = Modifier
                is UiStatus.Failed -> {
                        onRetry = { viewModel.retry() },
                        modifier = Modifier
                UiStatus.Success -> Unit
Enter fullscreen mode Exit fullscreen mode


Top comments (0)