Open-source project
Kotlin-Android-Open-Source/MVI-Coroutines-Flow avatar
Kotlin-Android-Open-Source/MVI-Coroutines-Flow

MVI-Coroutines-Flow: an Android sample of MVI built on Kotlin Coroutines Flow

Play MVI with Kotlin Coroutines Flow | MVI pattern on Android using Kotlin Coroutines Flow | Dagger Hilt DI | Koin DI | SharedFlow | StateFlow | Arrow.kt Android Sample

1,115 stars117 forksKotlinMIT

At a glance

What is it?
MVI-Coroutines-Flow is a multi-module Android sample that applies the Model-View-Intent loop to Kotlin Coroutines Flow, with Clean Architecture layers, Arrow.kt style functional code and unit tests at the ViewModel, domain and data levels. It is a teaching repository rather than a framework, and its dependency injection story is split across two branches.
Who is it for?
Use MVI-Coroutines-Flow as a reference for how MVI and Kotlin Coroutines Flow fit together in a real multi-module Android project, and read it as a sample rather than as a library to depend on. Do not adopt it as production infrastructure, since it is a demonstration app with a sample user list, and do not plan a Hilt based project around the dagger_hilt branch, which the documentation marks obsolete.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Kotlin, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem: a screen that owns its own state

Android development has a long standing problem with screen state. A view holds a reference to its data, the data layer pushes updates into it, and the view decides when to redraw. The logic that decides what should happen on screen ends up split between the activity, the fragment, the view model and the repository, and the direction of the data flow changes depending on which of them you are looking at.

The MVI pattern is an answer to that, and the project documentation explains it in full. The pattern was specified by André Medeiros, also known as Staltz, for a JavaScript framework called cycle.js. In theoretical terms, the Model-View-Intent pattern has three functions, and the project describes what each one does.

intent() takes the input from the user, meaning UI events such as clicks, and translates it into something that will be passed as a parameter to the model function. model() takes the output of intent() and manipulates the Model, producing a new Model, meaning a state that has changed. view() takes the model returned from model() and hands it to the view, which displays it.

The audience is therefore Android developers who want to see a unidirectional data flow implemented rather than argued about, and the repository is explicit that it is a sample: the screenshots show a list of users, an add user screen and a search screen, in a light and a dark theme, captured on a Pixel 3 XL running API 30.

The mechanism: immutability as the rule, not a recommendation

The part of the pattern this project emphasises hardest is immutability. The documentation is blunt about it: model() should not update an already existing Model, we want immutability, we do not change an already existing Model object instance. Instead a new Model is created according to the changes described by the intent, and model() is the only piece of the code that is allowed to create a new Model object.

That constraint is what makes the flow predictable. If state objects are never mutated, then any state you hold is a snapshot, and a consumer that is holding an old snapshot is not looking at something that changed underneath it.

The other half of model() is the business logic. The documentation says model() calls the app's business logic, which could be an Interactor, a Usecase, a Repository, or whatever pattern or terminology you use in your app, and delivers a new Model object as the result. So the intent to change something becomes a pure function from intent to new state, and the side effects live behind that call.

The repository topics list the vocabulary this implementation uses: coroutines, coroutines-flow, sharedflow, stateflow, unidirectional-data-flow, mvi and mvi-clean-architecture, along with arrow-kt and dagger-hilt. The project title itself is the summary of the approach, Coroutine plus Flow equals MVI. The Flow is the carrier of state, and the pattern says the state may only be replaced, never edited in place.

The module layout: a Clean Architecture skeleton in the repository root

The top-level directory listing is the architecture, and it is the most useful thing to take from this repository.

There is an app/ module for the application itself, a core/ module and a core-ui/ module for shared code and shared UI, a domain/ module for the business logic that model() calls, and a data/ module for the data layer. Then there are three feature modules, feature-main/, feature-add/ and feature-search/, which correspond exactly to the three screens in the screenshots. There is a dedicated mvi/ module for the MVI machinery, a test-utils/ module, and buildSrc/ for the shared Gradle build logic.

The build files are Kotlin DSL: build.gradle.kts at the root, settings.gradle.kts, gradle.properties, a gradle/ directory and the gradlew and gradlew.bat wrappers. The mvi/ module being separate from the features is the interesting choice, because it means the pattern is implemented once and consumed by every feature rather than copied into each screen.

The domain/ and data/ split is the other structural signal. model() delegates to business logic, and that business logic lives in a module that knows nothing about the UI, which is what makes the unit testing story possible. The documentation claims unit tests for the MVI ViewModel, the domain layer and the data layer, and the repository carries a codecov badge, so the coverage is measured rather than asserted.

Two dependency injection strategies, and one obsolete branch

The dependency injection story is the part of this project that needs reading carefully, because the documentation is explicit that the two are alternatives rather than layers.

The master branch uses Koin for dependency injection. There is a separate dagger_hilt branch that uses Dagger Hilt, and the documentation marks that branch as obsolete, with a note that it will be updated as soon as possible. So the actively maintained line is Koin, and the Hilt variant is the one you should look at only for reference.

That is worth dwelling on, because the repository topics list both dagger-hilt and koin-adjacent tooling, and a reader arriving from a Dagger Hilt project will reasonably expect the main branch to use Hilt. It does not. If you want to see a Hilt version of this architecture, you are looking at code the author considers out of date.

The two branches exist because the sample is meant to be read as a menu of choices rather than as one blessed implementation. The other choice on the menu is functional programming with Arrow.kt, which the documentation lists alongside the multiple modules and Clean Architecture as the headline features. The repository is under the Kotlin-Android-Open-Source organisation, and the homepage link points at the maintainer's own profile rather than at a product site, which is a further hint that this is a personal reference project.

The build, the CI and the APK you can actually run

The CI configuration is unusually thorough for a sample, and it tells you what the author considers finished. There is a Build CI workflow, a separate Unit Tests CI workflow, a Qodana workflow, and a workflow that validates the Gradle wrapper, which is the check that stops a modified wrapper from being merged. A codecov badge reports coverage.

The environment the sample targets is stated in the badges: API 21 and above, and Kotlin 2.0.20. Those two numbers matter more for a sample than they would for a library, because they are what your IDE and your Gradle setup have to be able to build. A 2024 vintage sample pinned to Kotlin 2.0.20 will not open cleanly in an older toolchain, and the Gradle version, Android Gradle Plugin and the rest of the build configuration live in the Kotlin DSL files in the repository.

There is also a maintained dependency updater: renovate.json sits in the root, which is how a sample keeps its dependency versions from rotting in a repository that nobody builds every day.

And you can run it. The documentation links a nightly debug APK built by the CI, and there is a Gitter community link for discussion. For a repository whose audience is people who want to see the pattern working rather than to read about it, having a downloadable build is a real part of the offering.

Releases, licence and what this sample is actually for

The release history is short and irregular. There is 2.2.0 from 2024-08-25, 2.1.0 from 2022-01-31 and 2.0.0 from 2021-11-23. So the tagged releases are two years apart, with a long gap in the middle, while the repository itself is not archived and the last push was on 2026-10-15. That pattern says the project gets attention in bursts rather than on a schedule, which is what you would expect from a personal sample.

The licence is MIT, so the code can be reused in your own application, in a commercial product, without any copyleft obligation. For a reference implementation, that matters more than usual, because copying the module structure is the entire point.

The two related repositories are worth knowing about, because they extend the same idea. There is a Jetpack Compose version of this MVI with Coroutines Flow project, and a separate repository for pagination in a horizontal list inside a vertical list, also built on MVI and Flow. The maintainer is clearly using this sample as a vehicle for publishing patterns rather than as a product.

The two references cited in the documentation give you the theory if you want it: a post on the Model-View-Intent design pattern on Android with Kotlin, Coroutines, Flow and Compose, and a piece on reactive apps with Model-View-Intent.

So the honest summary is this. This is a well-organised, tested, permissively licensed demonstration of one architecture, with a clear explanation of the pattern and a downloadable build, maintained in bursts by one person. Read it as a map. Copy the parts that fit, and keep your own project boundaries.

Editorial conclusion

Use MVI-Coroutines-Flow as a reference for how MVI and Kotlin Coroutines Flow fit together in a real multi-module Android project, and read it as a sample rather than as a library to depend on. Do not adopt it as production infrastructure, since it is a demonstration app with a sample user list, and do not plan a Hilt based project around the dagger_hilt branch, which the documentation marks obsolete. Before you copy anything from it, check the module boundaries against your own application, read the intent, model and view implementation in the mvi/ module, and confirm that the Kotlin and Gradle versions it pins still match your toolchain.

Frequently asked questions

What is MVI-Coroutines-Flow?

It is an Android sample that plays the Model-View-Intent pattern with Kotlin Coroutines Flow, split into multiple modules with Clean Architecture, using functional programming with Arrow.kt. It includes unit tests for the MVI ViewModel, the domain layer and the data layer.

Does MVI-Coroutines-Flow use Dagger Hilt or Koin?

The master branch uses Koin for dependency injection. Dagger Hilt is used on a separate dagger_hilt branch, and the project documentation marks that branch as obsolete, noting that it will be updated as soon as possible.

Which Android and Kotlin versions does the sample target?

The badges state API 21 and above, and Kotlin 2.0.20. The build itself is driven by Kotlin DSL Gradle files with a buildSrc directory, and a workflow validates the Gradle wrapper.

Can I run the MVI-Coroutines-Flow sample?

Yes. The project documentation links a downloadable debug APK built by the CI, and the repository also carries a Gitter community link. Otherwise you can clone the repository and build the app module with the Gradle wrapper.

What licence does MVI-Coroutines-Flow use?

MIT, with the LICENSE file at the root of the repository, so the sample code can be reused in commercial applications without any copyleft obligation.

Official sources

  1. Kotlin-Android-Open-Source/MVI-Coroutines-Flow on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases

Was this useful?