Skip to content

jreznot/flowik

Repository files navigation

Flowik

License team JetBrains project

Flowik is a small Kotlin library that brings MobX-style reactive state to JVM UI toolkits. You define plain observable values and computed expressions; the UI subscribes itself and re-renders automatically when anything it reads changes — no listeners, no manual rebinding.

The library is split into a framework-agnostic core and two UI-binding modules:

Module Purpose
flowik-core Reactive primitives: observables, computed, autoRun, reactions, actions
flowik-swing Bindings for Swing components (depends on flowik-core)
flowik-vaadin Bindings for Vaadin Flow components (depends on flowik-core)

Most users only need one of flowik-swing or flowik-vaadin — both pull flowik-core in transitively.

The MobX concept

Flowik is directly inspired by MobX. The core idea, in one sentence:

Anything that can be derived from the application state should be derived. Automatically.

In practice this means three building blocks:

  • Observable state — mutable values that track who reads them. Created with observable(...), observableMap(...), observables(...).
  • Derivations — pure functions over observables. Either computed { ... } for cached values or autoRun { ... } for side effects (typically UI updates). Both are self-wiring: they re-evaluate when — and only when — something they read has changed.
  • Actionsaction { ... } blocks that batch writes so derivations fire once at the end.

There is no event bus, no dependency graph you maintain by hand, no .subscribe() boilerplate at every call site. Read an observable from inside a derivation, and you are subscribed; stop reading it and you are not. For background and rationale, the MobX docs are the best introduction — the mental model carries over directly.

Getting it via JitPack

Releases are published through JitPack. The first time a version is requested, JitPack builds it from the git tag — the badge page above shows the latest available tag and triggers a build for new ones.

Gradle (Kotlin DSL)

repositories {
    mavenCentral()
    maven("https://jitpack.io")
}

dependencies {
    // pick one of these:
    implementation("com.github.jreznot.flowik:flowik-swing:v0.1.0")
    implementation("com.github.jreznot.flowik:flowik-vaadin:v0.1.0")
}

Maven

<repositories>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
</repositories>

<dependency>
    <groupId>com.github.jreznot.flowik</groupId>
    <artifactId>flowik-swing</artifactId> <!-- or flowik-vaadin -->
    <version>v0.1.0</version>
</dependency>

flowik-core is pulled in transitively. You only need to declare it explicitly if you want the reactive primitives without any UI bindings.

To follow the latest commit instead of a tagged release, use main-SNAPSHOT as the version.

A minimal example

A typical Flowik app has two parts: a store (plain Kotlin class holding observables and computed values) and a view that reads from the store. The view never registers listeners — it just reads, and Flowik does the rest.

import flowik.core.*

class CounterStore {
    val count = observable(0)
    val doubled = computed { count.value * 2 }

    fun inc() = action { count.value += 1 }
}

Using it from Swing (flowik-swing)

import flowik.core.*
import flowik.layout.uiFrame
import flowik.swing.*

fun main() {
    val store = CounterStore()
    uiFrame("Counter", width = 240, height = 120) {
        center {
            vbox(gap = 4) {
                Label { "Count: ${store.count.value} (×2 = ${store.doubled.value})" }
                Button("Increment") { store.inc() }
            }
        }
    }
}

The Label lambda reads store.count and store.doubled, so the label re-renders whenever either changes. No explicit subscription.

Using it from Vaadin (flowik-vaadin)

import com.vaadin.flow.component.html.Span
import com.vaadin.flow.component.orderedlayout.VerticalLayout
import com.vaadin.flow.router.Route
import flowik.core.*
import flowik.vaadin.*

@Route("counter")
class CounterView : VerticalLayout() {
    private val store = CounterStore()

    init {
        val label = Span()
        label.text { "Count: ${store.count.value} (×2 = ${store.doubled.value})" }
        add(label, Button("Increment") { store.inc() })
    }
}

The Vaadin bindings also expose two-way property binding for inputs — for example TextField().apply { value(store::filter) } keeps a TextField in sync with an observable string property.

Core API at a glance

From flowik-core:

val name    = observable("Alice")              // ObservableValue<String>
val person  = observable(Person("Bob", 30))    // ObservableMap<Person> — each property reactive
val items   = observables<TodoItem>()          // ObservableMapList<TodoItem>

val greeting = computed { "Hello, ${name.value}" }

autoRun { println(greeting.value) }            // re-runs whenever greeting changes
reaction(supply = { name.value }, effect = { newName -> println("name -> $newName") })
whenThen(check = { items.size > 10 }, effect = { println("Over the limit!") })

action {                                       // batch — derivations fire once at the end
    name.value = "Carol"
    person[Person::age].value = 31
}

Full examples and source

Real, runnable demos live in the repo:

  • flowik-swing-demo — a Todo app showing list filtering, computed totals, keyboard navigation, and conditional visibility.
  • flowik-vaadin-demo — the same Todo app on Vaadin Flow with property delegates and two-way input bindings.

Library sources:

License

Apache License 2.0 — see LICENSE.

About

Robust MobX for mutable UI on JVM: Swing, Vaadin, IntelliJ Platform

Resources

License

Stars

Watchers

Forks

Contributors