A simple, classic Kotlin MVI implementation based on coroutines with Android support, clean DSL and easy to understand logic

Related tags

Kotlin FlowMVI
Overview

Flow MVI

This is an MVI implementation based on coroutines with a few main goals:

  1. Being simple to understand, implement and use
  2. Following the Principle of Least Responsibility - all communication happens through strictly defined contract
  3. Featuring a clean and readable DSL

Let's get started:

Choose your fighter dependency:

val flowVersion = /* look at the widget */
implementation("com.github.Nek-12.FlowMVI:core:${flowVersion}") //does not depend on any particular platform
implementation("com.github.Nek-12.FlowMVI:android-compose:${flowVersion}") //For Jetpack Compose Android projects
implementation("com.github.Nek-12.FlowMVI:android-view:${flowVersion}") //For View-based Android projects

Core:

sealed class ScreenState : MVIState {
    object Loading : ScreenState()
    data class Error(e: Exception) : ScreenState()
    data class DisplayingCounter(
        val counter: Int,
    ) : ScreenState()
}

sealed class ScreenIntent : MVIIntent {
    object ClickedCounter : ScreenIntent()
}

sealed class ScreenAction : MVIAction {
    data class ShowMessage(val message: String) : ScreenAction()
}


val store = MVIStore<ScreenState, ScreenIntent, ScreenAction>(
    scope = viewModelScope,
    initialState = DisplayingCounter(0),
    reduce = { intent -> /*...*/ }
)

//somewhere in the ui layer

store.subscribe(
    coroutineScope,
    consume = { action -> /* ... */ },
    render = { state -> /* ... */ }
)

Android (Compose):

(), //use your fav DI framework ) { state -> consume { action -> when (action) { is ShowMessage -> { /* ... */ } } } when (state) { is DisplayingCounter -> { Button(onClick = { send(ClickedCounter) }) { Text("Counter: ${state.counter}") //render state, } } } }">
class ScreenViewModel : MVIViewModel<ScreenState, ScreenIntent, ScreenAction>() {

    override val initialState get() = Loading
    override fun recover(from: Exception) = Error(from) //optional

    override suspend fun reduce(intent: ScreenIntent) = when (intent) {

        //no-op if state is not DisplayingCounter
        is ClickedCounter -> withState<DisplayingCounter> { //this -> DisplayingCounter

            send(ShowMessage("So simple!"))

            copy(counter = counter + 1)
        }
        /* ... */
    }
}

@Composable
fun ComposeScreen() = MVIComposable(
    provider = getViewModel<ScreenViewModel>(), //use your fav DI framework
) { state ->

    consume { action ->
        when (action) {
            is ShowMessage -> { /* ... */
            }
        }
    }

    when (state) {
        is DisplayingCounter -> {
            Button(onClick = { send(ClickedCounter) }) {
                Text("Counter: ${state.counter}") //render state,
            }
        }
    }
}

If you don't want to use MVIComposable, just collect the actions flow using coroutineScope and render states using viewModel.states.collectAsState()

Android (View):

//ViewModel and Model classes have not changed

class ScreenFragment : Fragment(), MVIView {

    override val provider by viewModel<ScreenViewModel>()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        subscribe() //one-liner for store subscription. Lifecycle-aware and efficient.
    }

    override fun render(state: ScreenState) {
        //update your views
    }

    override fun consume(action: ScreenAction) {
        //handle actions
    }
}

And that's it!
If you don't like base classes, interfaces or abstraction, there always are ways to avoid inheritance and use composition. You are not required in any way to extend MVIView or MVIViewModel, or even MVIProvider. Everything is possible with a couple of lambdas. For examples of such implementations, see sample app or read java docs.

For more information and more elaborate examples, see the sample app.

License

Copyright 2022 Nikita Vaizin

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
You might also like...
Kotlin parser library with an easy-to-use DSL
Kotlin parser library with an easy-to-use DSL

Pratt Library for parsing expressions and a beautiful Kotlin DSL Just define your operators and operands with the Kotlin DSL and the parser is ready!

Opinionated Redux-like implementation backed by Kotlin Coroutines and Kotlin Multiplatform Mobile

CoRed CoRed is Redux-like implementation that maintains the benefits of Redux's core idea without the boilerplate. No more action types, action creato

A small DSL to make building a conversation in Bukkit easy.

Konversation Konversation provides a simple builder to construct chains of prompts to be used in the conversation API present in Bukkit. Bukkit only p

Searchbook - SearchBooks is an app built with Jetpack Compose and architecture of MVVM + MVI
Searchbook - SearchBooks is an app built with Jetpack Compose and architecture of MVVM + MVI

SearchBooks SearchBooks is an app built with Jetpack Compose and architecture of

🍭 GithubSearchKMM - Github Repos Search - Android - iOS - Kotlin Multiplatform Mobile using Jetpack Compose, SwiftUI, FlowRedux, Coroutines Flow, Dagger Hilt, Koin Dependency Injection, shared KMP ViewModel, Clean Architecture
🍭 GithubSearchKMM - Github Repos Search - Android - iOS - Kotlin Multiplatform Mobile using Jetpack Compose, SwiftUI, FlowRedux, Coroutines Flow, Dagger Hilt, Koin Dependency Injection, shared KMP ViewModel, Clean Architecture

GithubSearchKMM Github Repos Search - Kotlin Multiplatform Mobile using Jetpack Compose, SwiftUI, FlowRedux, Coroutines Flow, Dagger Hilt, Koin Depend

FaceTimeClone app that implements Coroutines , mvvm architecture , clean architecture , navigation component , hilt , etc.... using kotlin language
FaceTimeClone app that implements Coroutines , mvvm architecture , clean architecture , navigation component , hilt , etc.... using kotlin language

This repository contains a FaceTimeClone app that implements Coroutines , mvvm architecture , clean architecture , navigation component , hilt , etc.... using kotlin language

MVI Architecture Android Beginners: Sample App
MVI Architecture Android Beginners: Sample App

MVI Architecture Android Beginners: Sample App This repository contains a sample app that implements MVI architecture using Kotlin, ViewModel, LiveDat

Recycler-coroutines - RecyclerView Auto Add Data Using Coroutines

Sample RecyclerView Auto Add With Coroutine Colaborator Very open to anyone, I'l

MVIExample - A sample app showing how to build an app using the MVI architecture pattern
MVIExample - A sample app showing how to build an app using the MVI architecture pattern

MVIExample A sample app showing how to build an app using the MVI architecture p

Releases(1.0.0-alpha05)
  • 1.0.0-alpha05(Dec 10, 2022)

  • 1.0.0-alpha01(Nov 23, 2022)

    What's Changed

    • 1.0.0-alpha01: Safe state update API. by @Nek-12 in https://github.com/Nek-12/FlowMVI/pull/3

    There are a lot of changes in this release. It's currently experimental and the API may change further. I've decided to implement a synchronous first-in-first-serve state update api, and because of that, all the APIs that exposed current state without properly handling the parallelism of updates were removed.

    Breaking changes:

    • Removed currentState. Use withState() instead.
    • Added withState() function that obtains a current state, then runs the given block while suspending every other client from accessing the state until that block is finished. This is done because by obtaining state and then mutating it, you inherently introduce race conditions to your code, and now the management of that is being done out of the box by the library. Previously, you had to keep track of those yourself
    • State can now still be obtained and assigned as the state property, but these APIs are considered delicate and may be removed in the future if no use cases for them are found.
    • added new inline functions updateState and withState that work differently from the withState we were used to use. withState now operates on a state, but does not change it, and updateState updates the state with the return value of its block. Proper contracts were implemented for them.
    • There are now new inline functions for updateState and withState that work the same as the previous withState function - if state does not match given type, the block isn't executed and state is not updated.
    • reduce() now has no return value. This is because users of the library don't always need to update the state. Often an intent reduction results just in an action or other processing being done.
    • removed set(state) from the api
    • launchForState is now called launchRecovering and does not require a state return value. It's now exposed via store and it's reducer scope
    • Introduced new typealiases for lambdas of recover and reduce
    • The preferred way of creating and launching the store is lazyStore() (not launched) and launchedStore() (created and launched on first access)
    • MVIIntentScope no longer implements CoroutineScope but rather has a scope property
    • KMM migration plans are set in place. The library will get to the beta when the KMM migration is done for the core module.
    • Tests are now fully passing. Code coverage is on its way.
    • Store.launch() is now Store.start()
    • MVIIntentScope no longer implements MVIProvider to not let users abuse some functions like sending intents out of the reduce block anymore.
    • All deps were updated to latest stable versions, namely kotlin (1.7.21) and compose (1.3.1 with compiler 1.4.0-alpha02)

    Full Changelog: https://github.com/Nek-12/FlowMVI/compare/0.2.6...1.0.0-alpha01

    Source code(tar.gz)
    Source code(zip)
  • 0.2.6(Oct 7, 2022)

    What's Changed

    • 0.2.6 by @Nek-12 in https://github.com/Nek-12/FlowMVI/pull/2

    Full Changelog: https://github.com/Nek-12/FlowMVI/compare/0.2.5...0.2.6

    Source code(tar.gz)
    Source code(zip)
Owner
Nek.12
Android & Python Developer from Belarus
Nek.12
Kotlin-client-dsl - A kotlin-based dsl project for a (Client) -> (Plugin) styled program

kotlin-client-dsl a kotlin-based dsl project for a (Client) -> (Plugin) styled p

jackson 3 Dec 10, 2022
Architecture With MVI using Kotlin, Coroutines, Retrofit and Unit test

Architecture With MVI using Kotlin, Coroutines, Retrofit and Unit test MVI (Model-View-Intent) streamlines the process of creating and developing appl

Ahmed Karam 4 Aug 18, 2022
AbstractMvp 0.8 0.0 Kotlin is a library that provides abstract components for MVP architecture realization, with problems solutions that are exist in classic MVP.

MinSDK 14+ AbstractMvp AbstractMvp is a library that provides abstract components for MVP architecture realization, with problems solutions that are e

Robert 12 Apr 5, 2022
In this project, I tried to understand and implement the architecture suggested by Android.

Shopping App Bu projede, Android'in önerdiği modern mimariyi anlamaya ve uygulamaya çalıştım. Projede kullandığım teknolojiler, Room Retrofit Coroutin

Süveybe Sena Küçük 24 Aug 11, 2022
A Mindustry mod adding turrets from older versions of Mindustry, specifically Mindustry Classic

Mindustry Kotlin Mod Template A Kotlin Mindustry mod that works on Android and PC. This is equivalent to the Java version, except in Kotlin. Building

null 4 Sep 3, 2022
Kotlin Multiplatform is an SDK for cross-platform mobile development, which enables teams to use the same business logic in both Android and iOS client applications.

Kotlin Multiplatform is an SDK for cross-platform mobile development, which enables teams to use the same business logic in both Android and iOS client applications.

Chris Russell 1 Feb 11, 2022
Kotlin Multiplatform lifecycle-aware business logic components (aka BLoCs) with routing functionality and pluggable UI (Jetpack Compose, SwiftUI, JS React, etc.), inspired by Badoos RIBs fork of the Uber RIBs framework

Decompose Please see the project website for documentation and APIs. Decompose is a Kotlin Multiplatform library for breaking down your code into life

Arkadii Ivanov 819 Dec 29, 2022
🔓 Kotlin version of the popular google/easypermissions wrapper library to simplify basic system permissions logic on Android M or higher.

EasyPermissions-ktx Kotlin version of the popular googlesample/easypermissions wrapper library to simplify basic system permissions logic on Android M

Madalin Valceleanu 326 Dec 23, 2022
An example of a test task for creating a simple currency converter application for the Android platform. The app is developed using Kotlin, MVI, Dagger Hilt, Retrofit, Jetpack Compose.

Simple Currency Converter Simple Currency Converter Android App by Isaev Semyon An example of a test task for creating a simple currency converter app

Semyon Isaev 1 Nov 8, 2021