Patrick Jackson · blog

ReduxKotlin 1.0

1,138 words6 min read#kotlin#kotlin-multiplatform#redux#reduxkotlin#announcement

The first 1.0 pre-releases of ReduxKotlin are on Maven Central. The last time I wrote about this library in earnest, the big news was a thread-safe store and a 0.5.1 version number. Since then the library has grown a concurrent store, field-level subscriptions, Compose bindings, three flavors of DevTools, and (this one surprised me too) a command-line tool.

Something else changed in that gap, and it shaped more of this release than I expected: most of us don’t hand-write this code line by line anymore. We describe what we want, an agent writes it, and the real work moves to the loop around it. Can the agent tell whether what it wrote actually worked? How many cycles does it burn finding out? A state library is either useful in that loop or it’s friction, and a lot of the 1.0 cycle went into making ReduxKotlin the first thing.

This post is the map. Each of the eight that follow takes one piece and goes deeper.

Start here

build.gradle.kts
implementation("org.reduxkotlin:redux-kotlin-bundle-compose:1.0.0-alpha04")

That’s the whole installation for a Compose Multiplatform app. One line pulls in the concurrent store, the store registry, the routing DSL, multi-model state, granular subscriptions, the Compose bindings, and state persistence.

Not using Compose? Drop -compose:

implementation("org.reduxkotlin:redux-kotlin-bundle:1.0.0-alpha04")

And if you prefer to pick modules by hand, there’s a BOM that keeps the versions aligned:

implementation(platform("org.reduxkotlin:redux-kotlin-bom:1.0.0-alpha04"))
implementation("org.reduxkotlin:redux-kotlin")
implementation("org.reduxkotlin:redux-kotlin-concurrent")

Why a bundle

ReduxKotlin now publishes 21 libraries plus a BOM. That’s a great menu and a terrible first impression.

The modules exist for a real reason. The original goal of the project was to keep the core tiny and let everything else be opt-in, and I still believe in that. A JS-only consumer should not pay for AtomicFu. But “small core, many modules” is a library-author’s virtue, not a user’s, and asking someone to name seven artifacts before they can dispatch an action is a bad trade.

So the bundles are the front door. redux-kotlin-bundle-compose is what I would pick for a new app, and you can always unbundle later: the modules underneath didn’t go anywhere. Details are in the Bundles doc.

Why this still fits multiplatform

State management is the least platform-specific thing in an app, and it’s the thing every platform reimplements anyway. A reducer is a pure function from (state, action) to state, which makes it about as portable as code gets.

In practice this holds up better than I expected. In the TaskFlow sample app (a Kanban board that builds for Android, iOS, desktop, and the browser) the entire Redux layer lives in commonMain with exactly one platform shim, and that shim only decides which thread your callbacks land on. Everything else is shared.

The target list is longer than it used to be and, honestly, shorter in a couple of places. Part 2 has the real matrix, including what got dropped.

Why this fits coding agents

I didn’t set out to build a library for agents. But the properties that make Redux tedious for a human turn out to be exactly the properties a machine wants:

  • One mutation point. There’s exactly one place state changes. An agent doesn’t have to find it.
  • Actions are data. The full list of things your app can do is enumerable.
  • State is a serializable tree. It can be dumped, diffed, and asserted on.
  • Reducers are pure. An agent that can’t run your app can still test your entire state layer.

Legibility alone isn’t enough, though. The failure mode I kept watching was an agent writing plausible code, having no way to tell whether it worked, and looping. So a lot of the 1.0 work went into feedback channels that don’t require a human to look at a screen: machine-readable DevTools output, and a snapshot tool that renders your real UI from seeded state and diffs it against a golden. Part 9 is the whole argument.

What actually changed

ConcurrentStore replaces ThreadSafeStore. The thread-safe store I recommended in 2020 put a lock around every store function, including getState. It was correct and quietly expensive: a read from any thread blocked for the entire duration of an in-flight dispatch. The new store keeps writes serialized but makes reads lock-free. redux-kotlin-threadsafe is deprecated.

Granular subscriptions. store.subscribe { } fires on every dispatch, which means everyone writes the same “re-read the value, compare it to the last one, remember the new one” boilerplate. subscribeTo does the diffing for you and only calls back when the value actually moved.

Compose bindings. store.selectorState { } and store.fieldState(Model::field) give you a Compose State<T> scoped to exactly the slice a component renders. No provider, no CompositionLocal, no hooks, you pass the store as a parameter.

State persistence. A StateSaver that rides Compose’s saved-state machinery, so an Android app survives both rotation and process death. It restores by dispatching an action, and the reason why is more interesting than it sounds.

DevTools. An in-app debug drawer, a desktop monitor with time travel, and a CLI. The original Redux DevTools browser extension still works too, which pleases me more than it probably should.

Snapshot testing. rk snapshot renders your real Compose screens headlessly from seeded store state (f(state) → PNG) and diffs against goldens. Every TaskFlow screenshot in this series was generated with it.

The alpha caveats

  • The current release is 1.0.0-alpha04. Parts of the website and README still say alpha01. The docs lag the code and I’m working through it.
  • DevTools and snapshot are experimental and exempt from semver until they stabilize. They will move.
  • The rk binary’s macOS bottle is Apple Silicon only right now. Intel Mac users need to build from source.
  • Everything else (core, concurrent, granular, multimodel, routing, compose) is the API I intend to ship at 1.0.

The series

  1. ReduxKotlin 1.0 (this post)
  2. One Store, Nine Targets, the real KMP target matrix
  3. ConcurrentStore: Reads That Don’t Wait
  4. Granular Subscriptions: Stop Diffing By Hand
  5. ReduxKotlin + Compose: Bind the Narrowest Slice
  6. Surviving Rotation and Process Death
  7. Redux DevTools, Outside the Browser
  8. Screenshot Tests Where the Input Is State
  9. ReduxKotlin for Coding Agents

Upgrade your mobile agent loops today!


Sources of truth: reduxkotlin.org · github.com/reduxkotlin/redux-kotlin · the TaskFlow sample

Part 1 of ReduxKotlin 1.0.