MVU for Kotlin Multiplatform

Overview

Oolong

Build Status Maven Central Sonatype Nexus (Snapshots) License Slack chat

Oolong is an Elm inspired Model-View-Update (MVU) implementation for Kotlin multiplatform. As the name implies, three core concepts comprise the foundation of this architecture:

  • Model - a type to represent the program state

  • View - a function to map the state to view properties

  • Update - a function to update the state

By applying this simple pattern you can create composable, testable programs that can run on any platform. Oolong enables a common codebase for all platforms by using a render function which is implemented by each frontend.

Documentation

Get started with Oolong by reading the official guide.

Download

dependencies {
    implementation("org.oolong-kt:oolong:2.1.0")
}
Comments
  • Remove ': Any' bounds in generic types

    Remove ': Any' bounds in generic types

    Is your feature request related to a problem? Please describe.

    In Kotlin, we have the chance to have a compiler that makes safe to deal with nullability. Therefore, unlike in Java where null is very dangerous value that must be avoided at all cost, in Kotlin null is a valid and useful value to represent the absence of something. Yet the compiler will help us to make sure we treat it safely. On other words, the null of Kotlin is the equiavlent of Nothing in Elm: Useful and safe to use.

    Yet, many generic functions in oolong force the generic argument to be not null. Example: fun <A : Any, B : Any> map(effect: Effect<A>, f: (A) -> B): Effect<B>

    This adds an unecessary constraint on something that could have been safely nullable.

    It is also viral and will cause any helpers built on top to also add that generic boundary. Example:

    // Doesn't compile unless adding `: Any` bound to `T` and `R`
    fun <T, R> Effect<T>.map(transform: (T) -> R): Effect<R> = map(this, transform)
    

    By the way, as a side question: why not making map an extension function on Effect? It would be easier to discover and natural, since in Kotlin we're use to map over collections, sequences, flows, etc.

    I aggree, that it is probably very uncomon to use nullable types for model or messages. But, in a world of safe nullability, I don't see why it should completly be prevented by the framework. In elm for instance, one can freely use Maybe for model and messages.

    Describe the solution you'd like Remove uncessary : Any bound on generic arguments.

    Describe alternatives you've considered For a usage point of view, I can use Optional when I want deal with nullability in model or message. But it is not very idiomatic, we have nullability directly in the Kotlin type system.

    opened by jcornaz 7
  • [WIP] Add navigation component.

    [WIP] Add navigation component.

    Adds an abstraction over navigation which handles route changing, component delegation, and state caching. Example usage in the README.

    Let's discuss! @oolong-kt/developers

    opened by pardom 7
  • Support for js platform

    Support for js platform

    Hi, I wanted to test Oolong on JavaScript.

    I added the target and to implement runBlocking for the tests I removed the returned type. I also added `@JsName()' for test methods, the js platform doesn't support spaces in method names.

    I don't know if it is the good way to do it but I was able to play with Oolong :).

    I'm using Bulma and bulma-kotlin: Capture d’écran 2020-06-01 à 08 02 41

    opened by jeancharles-roger 5
  • Effect breaks Android Build

    Effect breaks Android Build

    Describe the bug Android application fails to build when "effect" is used

    To Reproduce I've made the following repo to demonstrate the issue - Oolong Test

    • "master" fails to build
    • "working" builds successfully because the "effect" code in Store.kt is commented out

    Additional context I'm using oolong-jvm because this is just going to run on Android and doesn't require using Kotlin Multi-Platform. I am unsure if that is related because everything else appears to work as expected

    opened by dladukedev 5
  • Documentation site alternatives

    Documentation site alternatives

    Currently the project site is generated with GitBook. Let's investigate alternatives that allow more flexibility.

    Alternatives:

    • https://docusaurus.io
    • https://www.docz.site
    • https://docsify.js.org

    Please leave your suggestions below.

    opened by pardom 3
  • Add javascript targets, support for MacOS arm64 architecture, and upgrade Kotlin.

    Add javascript targets, support for MacOS arm64 architecture, and upgrade Kotlin.

    Summary of changes:

    • Includes an update to Kotlin 1.6.21
    • Tests updated to no longer use deprecated APIs
    • JS artifacts using IR backend (excludes legacy backend) for both browser and nodejs
    • MacOS arm64 architecture support

    note: atomicfu is required to compile JS targets with the IR backend and with coroutines version 1.6.2; a fix is coming in the next release, but I opted to downgrade coroutines to make project configuration easier in the short term.

    opened by noheltcj 2
  • Bump mixin-deep from 1.3.1 to 1.3.2 in /oolong

    Bump mixin-deep from 1.3.1 to 1.3.2 in /oolong

    Bumps mixin-deep from 1.3.1 to 1.3.2.

    Commits
    Maintainer changes

    This version was pushed to npm by doowb, a new releaser for mixin-deep since your current version.


    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot ignore this [patch|minor|major] version will close this PR and stop Dependabot creating any more for this minor/major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 1
  • Bump handlebars from 4.1.2 to 4.5.3 in /oolong

    Bump handlebars from 4.1.2 to 4.5.3 in /oolong

    Bumps handlebars from 4.1.2 to 4.5.3.

    Changelog

    Sourced from handlebars's changelog.

    v4.5.3 - November 18th, 2019

    Bugfixes:

    • fix: add "no-prototype-builtins" eslint-rule and fix all occurences - f7f05d7
    • fix: add more properties required to be enumerable - 1988878

    Chores / Build:

    • fix: use !== 0 instead of != 0 - c02b05f
    • add chai and dirty-chai and sinon, for cleaner test-assertions and spies, deprecate old assertion-methods - 93e284e, 886ba86, 0817dad, 93516a0

    Security:

    • The properties __proto__, __defineGetter__, __defineSetter__ and __lookupGetter__ have been added to the list of "properties that must be enumerable". If a property by that name is found and not enumerable on its parent, it will silently evaluate to undefined. This is done in both the compiled template and the "lookup"-helper. This will prevent new Remote-Code-Execution exploits that have been published recently.

    Compatibility notes:

    • Due to the security-fixes. The semantics of the templates using __proto__, __defineGetter__, __defineSetter__ and __lookupGetter__ in the respect that those expression now return undefined rather than their actual value from the proto.
    • The semantics have not changed in cases where the properties are enumerable, as in:
    {
      __proto__: 'some string'
    }
    
    • The change may be breaking in that respect, but we still only increase the patch-version, because the incompatible use-cases are not intended, undocumented and far less important than fixing Remote-Code-Execution exploits on existing systems.

    Commits

    v4.5.2 - November 13th, 2019

    Bugfixes

    • fix: use String(field) in lookup when checking for "constructor" - d541378
    • test: add fluent API for testing Handlebars - c2ac79c

    Compatibility notes:

    • no incompatibility are to be expected
    ... (truncated)
    Commits
    • c819c8b v4.5.3
    • 827c9d0 Update release notes
    • f7f05d7 fix: add "no-prototype-builtins" eslint-rule and fix all occurences
    • 1988878 fix: add more properties required to be enumerable
    • 886ba86 test/chore: add chai/expect and sinon to "runtime"-environment
    • 0817dad test: add sinon as global variable to eslint in the specs
    • 93516a0 test: add sinon.js for spies, deprecate current assertions
    • 93e284e chore: add chai and dirty-chai for better test assertions
    • c02b05f fix: use !== 0 instead of != 0
    • 8de121d v4.5.2
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot ignore this [patch|minor|major] version will close this PR and stop Dependabot creating any more for this minor/major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 1
  • Effect batch is not asynchronous

    Effect batch is not asynchronous

    Currently, Effect functions are suspended, however this does not prevent them from blocking when combined with batch. The batch function iterates over and calls each effect. Given the current implementation, each Effect will block the iteration until completed:

    typealias Effect<Msg> = suspend (dispatch: Dispatch<Msg>) -> Any?
    
    fun <Msg> batch(effects: Iterable<Effect<Msg>>): Effect<Msg> =
        { dispatch -> for (effect in effects) effect(dispatch) }
    

    Instead, we should define Effect in the context of a CoroutineScope and launch each Effect as we iterate over them.

    typealias Effect<Msg> = suspend CoroutineScope.(dispatch: Dispatch<Msg>) -> Any?
    
    fun <Msg> batch(effects: Iterable<Effect<Msg>>): Effect<Msg> =
        { dispatch -> for (effect in effects) launch { effect(dispatch) } }
    
    opened by pardom 1
  • Consider alternative type and syntax for Next

    Consider alternative type and syntax for Next

    Is your feature request related to a problem? Please describe.

    I brought this up in Slack but would like to continue the conversation here to open it to other folks that would like to participate.

    The Next type is currently typealias'd to a Pair<Model, Effect<Msg>>. The usage of Pair comes from the desire to use the built in tuple types that the Kotlin language offers via generics and data class (Pair and Triple).

    Unfortunately, unlike Elm's tuples which have a nice syntax for creation with parentheses (e.g. (1, 2, "a", "b")), Kotlin relies on constructors for its data classes with no short-hand replacement. To work around that, the language offers an infix function to to aid in Pair's creation.

    This leads to idiomatic Oolong code in the Update/Init functions looking somewhat like this:

    val update: Update<Msg, Model> = { msg, model ->
      model to none()
    }
    

    This syntax can be a little hard to decipher, especially as a novice reader of the code. It requires some knowledge of the Oolong type definitions to understand why this syntax is being used. The keyword to reads more like a mapping than a union (to me, at least), and I think there's an opportunity here to be more explicit with types and have a more readable syntax.

    Describe the solution you'd like

    Give an explicit type to Next and provide a more ergonomic infix operator for its creation to lean in to the Kotlin language features and tools.

    Two steps to this solution:

    1. Redefine Next to data class Next<Model, Msg>(val model: Model, val effect: Effect<Msg>)
    2. Add a new infix function and: infix fun <Model, Msg> Model.and(effect: Effect<Msg>) = Next(this, effect)

    This changes our above example to read like this:

    val update: Update<Msg, Model> = { msg, model ->
        model and none()
    }
    

    Advantages:

    • the code reads as "return the updated model and these effects", which I think is a very nice improvement
    • extensions on Next will not pollute the Pair type in case there were extensions that were Oolong-specific
    • deconstructing the Next type will have named fields instead of first and second

    Cons:

    • lose access to any extensions on Pair
    • potentially breaking change to the type system

    note: I'd expect that we provide a to infix for compatibility initially, but have that deprecated

    @Deprecated(
        message = "prefer `and` operator, `to` will be removed in a future update",
        replaceWith = ReplaceWith("this.and(effect)")
    )
    infix fun <Model, Msg> Model.to(effect: Effect<Msg>) = Next(this, effect)
    

    The problem that this doesn't solve is the syntax for the optional Effect case, I think ideally you'd be able to have a syntax that supports returning just the model or returning the model and effects, e.g.

    val update: Update<Msg, Model> = { msg, model ->
        model
    }
    

    Not sure what might be available for that solution (outside of a compiler plugin that allows for a more ergonomic tuple creation syntax)

    Describe alternatives you've considered

    • with would be an acceptable alternative to and but it is already a reserved word in the Kotlin language
    • I opted against an infix operator (e.g. *) since that is not a standard Kotlin language feature and would likely be considered a bit obtuse in the way that to is today with the added negative of being less discoverable

    Additional context

    none

    opened by sddamico 1
  • Subscription support

    Subscription support

    Is your feature request related to a problem? Please describe.

    I want to subscribe to some events, that might be external to my system. Example:

    • Get an event every X seconds
    • Subscribe to events from a websocket

    Describe the solution you'd like

    Since the present library is inspired from elm, I'd find natural to imitate elm's subscription system (in Browser.element).

    That could be translated in Kotlin like this:

    data class Model(val ticks: Int = 0)
    sealed class Msg {
      object Tick : Msg()
    }
    
    val subscribe = Subscribe<Model, Msg> { model ->
      every(10.seconds).map { Msg.Tick }
    }
    
    // ...
    
    val dispose = Oolong.runtime(
        init,
        update,
        suscribe,
        view,
        render
    )
    

    Of course this is only a draft example, and any variation of the API would do.

    Describe alternatives you've considered

    I think I could start a coroutine from a disposable effect that will call dispatch to fire the messages.

    That would however require me to keep an instance of the dispose in my model, so that I can cancel it later.

    opened by jcornaz 2
  • Flipper Plugin

    Flipper Plugin

    Related links:

    • https://fbflipper.com/
    • https://fbflipper.com/docs/extending/index.html

    Please follow up with suggestions and ideas if you have them, @oolong-kt/developers.

    good first issue 
    opened by pardom 3
Releases(v2.1.1)
  • v2.1.1(Aug 13, 2022)

    Added

    • Update Kotlin to 1.7.10
    • Update Kotlin Coroutines to 1.6.4
    • Add a next builder function.
    • Runtime factory that passes dispatch to view instead of render.

    Changed

    • View function changed from (Model) -> Props as (Model, Dispatch<Msg>) -> Props.
    • Render function changed from (Props, Dispatch<Msg>) -> Any? to (Props) -> Any?.

    Deprecated

    • Runtime factories that pass dispatch to render instead of view.

    Removed

    • Remove deprecated type aliases.
    Source code(tar.gz)
    Source code(zip)
  • v2.1.0(Sep 30, 2020)

    Added

    • Add a runtime overload which combines view and render.
    • Update Kotlin to 1.4.10

    Changed

    • CoroutineDispatcher replaced with CoroutineContext in runtime builder.
    • Dispatch deprecated in favor of Job.
    • oolong.Oolong.runtime deprecated in favor of oolong.runtime.
    • disposableEffect deprecated.
    • Allow incoming types to be nullable.
    • Deprecate Next, Init, Update, View, and Render in preference of underlying types.
    Source code(tar.gz)
    Source code(zip)
  • v2.0.7(Sep 30, 2020)

  • v2.0.6(Jul 1, 2020)

  • v2.0.5(Jun 26, 2020)

  • v2.0.4(Jun 26, 2020)

  • v2.0.3(Jun 26, 2020)

    Added

    • Update Kotlin to 1.3.72
    • Update Kotlin Coroutines to 1.3.5

    Removed

    • Deprecated coroutine scope and context arguments in runtime creator function.
    • Remove deprecated runtime creator function.
    Source code(tar.gz)
    Source code(zip)
  • v2.0.2(Jun 26, 2020)

  • v2.0.1(Jun 26, 2020)

  • v2.0.0(Jun 26, 2020)

  • v1.0.0(Jun 26, 2020)

Model-View-ViewModel architecture components for mobile (android & ios) Kotlin Multiplatform development

Mobile Kotlin Model-View-ViewModel architecture components This is a Kotlin Multiplatform library that provides architecture components of Model-View-

IceRock Development 638 Jan 2, 2023
Extendable MVI framework for Kotlin Multiplatform with powerful debugging tools (logging and time travel), inspired by Badoo MVICore library

Should you have any questions or ideas please welcome to the Slack channel: #mvikotlin Inspiration This project is inspired by Badoo MVICore library.

Arkadii Ivanov 460 Dec 31, 2022
Redux implementation for Kotlin (supports multiplatform JVM, native, JS, WASM)

Redux-Kotlin ![badge][badge-ios] A redux standard for Kotlin that supports multiplatform projects. Full documentation at http://reduxkotlin.org. Misso

Redux-Kotlin 301 Dec 25, 2022
Unidirectional Data Flow in Kotlin - Port of https://github.com/ReSwift/ReSwift to Kotlin

ReKotlin Port of ReSwift to Kotlin, which corresponds to ReSwift/4.0.0 Introduction ReKotlin is a Redux-like implementation of the unidirectional data

null 538 Dec 13, 2022
MVVM RECIPE ANDROID APP Is an app where I show how to use MVVM, retrofit, dagger hilt, coroutine, liveData, Kotlin, navigation component, and so on...

MVVM RECIPE ANDROID APP Is an app where I show how to use MVVM, retrofit, dagger hilt, coroutine, liveData, kotlin, navigation component, and so on...

Isaias Cuvula 23 Dec 5, 2022
A sample project in Kotlin to demonstrate AndroidX, MVVM, Coroutines, Hilt, Room, Data Binding, View Binding, Retrofit, Moshi, Leak Canary and Repository pattern.

This repository contains a sample project in Kotlin to demonstrate AndroidX, MVVM, Coroutines, Hilt, Room, Data Binding, View Binding, Retrofit, Moshi, Leak Canary and Repository pattern

Areg Petrosyan 42 Dec 23, 2022
Android Clean Architecture💎 Base Project Android with Kotlin and MVVM applying clean architecture

Android Clean Architecture?? Base Project Android with Kotlin and MVVM applying clean architecture

Mina Mikhail 103 Dec 2, 2022
A sample to showcase Kotlin, MVVM, Koin, Coroutines, StateFlow, Room, WorkManager, Retrofit and Unit test.

TVMaze-Cache A sample to showcase Kotlin, MVVM, Koin, Coroutines, StateFlow, Room, WorkManager, Retrofit and Unit test. Features MVVM Architecture + R

Mohammadali Rezaei 25 Jul 21, 2022
📒Note taking app, MVVM with Google Architectural components Room, LiveData and ViewModel written in Kotlin, androidx libraries

?? MyNotes Note taking Android App using androidx libraries, MVVM with Google Architectural components Room, LiveData and ViewModel. Written in Kotlin

Akshat Bhuhagal 60 Dec 5, 2022
simple app used Kotlin MVVM Dagger2 Room Coroutines Retrofit2

Exhibits Application which retrieves data from Webserver (via Retrofit), saves it into Room and get from it if user is offline. There are applying MVV

Ahmed Eid 0 Oct 14, 2021
Clean Architecture - Kotlin, MVVM, Use cases

CleanArchitecture Is Clean Architecture only MVVM ? NO, MVVM is a part of clean architecture. MVVM includes Model, View and ViewModel and in addition

Deepanshi bajaj 25 Nov 29, 2022
Kotlin+Flow+Retrofit+OKHttp+ViewBanding+ViewModel+LiveData封装的一个Kotlin版本的MVVM框架

Gitee 地址:kmvvm Github 地址:kmvvm CHANGE LOG 技术要点 支持Flow+Retrofit+OkHttp实现链式http请求 支持Rxjava+Retrofit+OkHttp实现链式http请求 封装基类:BaseActivity、BaseVMActivity、Ba

抓猪 82 Dec 23, 2022
Ceci est une application d'actualités de l'architecture MVVM avec Kotlin

MVVM-Appli Infos Ceci est une application d'actualités de l'architecture MVVM avec Kotlin utilisant des composants : Retrofit, Room, Coroutines, et Na

Yannick Loic 0 Nov 4, 2021
An android app built using Kotlin following Multi-Module Clean Architecture MVVM

RickyandMorty An android app built using Kotlin that consumes RickyadMorty API to display characters.It has been built following Clean Architecture Pr

Kibet 14 Sep 2, 2022
JeTaxi is built on Clean Architecture-MVVM with Kotlin and follows modern android development trends.

JeTaxi is built on Clean Architecture-MVVM with Kotlin and follows modern android development trends. Also, It uses some of Jetpack and popular libraries. These are Kotlin Coroutine-Flow, kotlinx.serialization, Hilt, Compose, Accompanist, Retrofit2, OkHttp3, Chucker, MockWebServer, Truth.

Tolga Bolatcan 13 Nov 2, 2022
Make E-Commerce using Kotlin MVVM

Kotlin E-Commerce MVVM Make E-Commerce using Kotlin, and for backend i use Golang, and PHP Feature MVVM Retrofit2 15+ Screen Payment Gateway (Razorpay

Achmad Rizki Nur Fauzie 3 Jul 29, 2022
App kotlin with flow, paging 3, MVVM, Room, Dagger hilt

TMDBTest App kotlin with flow, paging 3, MVVM, Room, Dagger hilt Para compilar la app se tiene que descargar el proyecto. Luego poner la APIKEY de TMD

null 3 Sep 9, 2022
MVVM Kotlin Android Architecture

Model-View-ViewModel (ie MVVM) Model-View-ViewModel (ie MVVM) is a template of a client application architecture, proposed by John Gossman as an alter

Tuan 2 Mar 13, 2022
MVVM Android Studio Kotlin Project Dog Images

MVVM Android Studio Kotlin Project Dog Images This project implement: MVVM Retro

Teulys Jiménez 1 Apr 17, 2022