fusio

Decomposes Compose presenters, eliminating callback and event plumbing by generating compile-time routing between parent and child events/effects, enabling clean, callback-free fused sub-presenters.

Android
JVM
iOS
macOS
Wasm
JS
GitHub stars1
Authorskitakkun
Dependents0
LicenseApache License 2.0
Creation date5 months ago

Last activity5 months ago
Latest release0.1.0 (5 months ago)

Fusio

⚠️ Experimental — research project building on DroidKaigi 2024 / 2025-style Compose architectures.

Fusio — Latin, "to pour together, to fuse" — is a Kotlin compiler plugin that decomposes Compose presenters without the usual I/O boilerplate at the seams.

A @Composable presenter that grows past a few features wants to be split into smaller pieces. But splitting forces I/O boilerplate at every seam: callback lambdas the parent threads down to each child, event invocations the children fire back up to the parent. That boilerplate scales with every new feature you add.

Fusio resolves those seams at compile time — fusion in the literal sense. Two sealed-type annotations declare which parent event routes into which child and which child effect lifts back up as which parent effect; the compiler synthesises the callback-and-invocation plumbing between them. The decomposed presenters end up reading as plain functions — no callback parameters threaded through, no manual event forwarding.

The library's core data type, Presentation<State, Event, Effect>, is a state value, the effect stream the presenter produced alongside it, and a send: (Event) -> Unit entry point the UI uses to push input back in.

Quick start

Apply the plugin:

plugins {
    id("com.kitakkun.fusio") version "0.1.0"
}

Declare event/effect routing on the parent's sealed types and fuse sub-presenters inside buildPresenter:

sealed interface MyEvent {
    @MapTo(ChildEvent.Add::class) data class AddItem(val title: String) : MyEvent
}
sealed interface MyEffect {
    @MapFrom(ChildEffect.Added::class) data class ShowAdded(val title: String) : MyEffect
}

@Composable
fun PresenterScope<ChildEvent, ChildEffect>.childPresenter(): ChildState { /* … */ }

@Composable
fun myScreenPresenter(): Presentation<MyState, MyEvent, MyEffect> = buildPresenter {
    val list = fuse { childPresenter() }
    MyState(list.items)
}

UI calls presentation.send(MyEvent.AddItem("milk")); the event is routed into the child's on<ChildEvent.Add> handler, the child emits a ChildEffect.Added, and the parent surfaces it as MyEffect.ShowAdded on presentation.effectFlow. All routing plumbing is generated by the compiler plugin — no reflection.

The runnable demo lives in demo/:

cd demo
../gradlew runJvm

Documentation

  • Getting started — motivation, what you write, full Todo example walkthrough, project layout, build commands.
  • Testing — fusio-test headless harness, scenario API, sub-presenter testing, integration with Kotest / Turbine.
  • Implementation notes — for contributors touching fusio-runtime internals.
  • docs/ — additional design notes (event-handler exhaustiveness, multi-compiler-version layer, etc.).

Compatibility

Kotlin versions

Your Kotlin Supported
2.3.0 – 2.3.x ✅
2.4.0-Beta2+ ✅

A single fusio-compiler-plugin jar ships with a per-Kotlin-version compatibility layer inside it (a shaded fusio-compiler-compat + kXXX submodule pattern inspired by ZacSweers/Metro). The plugin inspects the running Kotlin compiler at startup and ServiceLoader- resolves the matching impl, so picking up a new patch needs no Fusio release.

Platform targets

fusio-annotations, fusio-runtime, and fusio-test publish to:

Target Status
JVM ✅
Android (com.android.kotlin.multiplatform.library, minSdk 21) ✅
iOS (iosArm64, iosSimulatorArm64) ✅
macOS (macosArm64) ✅
JS (js(IR) — browser & node) ✅
Wasm (wasmJs — browser & node) ✅

commonTest runs on all of the above. watchOS, tvOS, Linux, and Windows aren't configured yet but pose no fundamental obstacle.

License

Apache License, Version 2.0 — see LICENSE.

The design of the compiler-compat layer and the plugin's apply-time Kotlin-version warning are inspired by ZacSweers/Metro (also Apache 2.0). See NOTICE.

Android
JVM
iOS
macOS
Wasm
JS
GitHub stars1
Authorskitakkun
Dependents0
LicenseApache License 2.0
Creation date5 months ago

Last activity5 months ago
Latest release0.1.0 (5 months ago)

Fusio

⚠️ Experimental — research project building on DroidKaigi 2024 / 2025-style Compose architectures.

Fusio — Latin, "to pour together, to fuse" — is a Kotlin compiler plugin that decomposes Compose presenters without the usual I/O boilerplate at the seams.

A @Composable presenter that grows past a few features wants to be split into smaller pieces. But splitting forces I/O boilerplate at every seam: callback lambdas the parent threads down to each child, event invocations the children fire back up to the parent. That boilerplate scales with every new feature you add.

Fusio resolves those seams at compile time — fusion in the literal sense. Two sealed-type annotations declare which parent event routes into which child and which child effect lifts back up as which parent effect; the compiler synthesises the callback-and-invocation plumbing between them. The decomposed presenters end up reading as plain functions — no callback parameters threaded through, no manual event forwarding.

The library's core data type, Presentation<State, Event, Effect>, is a state value, the effect stream the presenter produced alongside it, and a send: (Event) -> Unit entry point the UI uses to push input back in.

Quick start

Apply the plugin:

plugins {
    id("com.kitakkun.fusio") version "0.1.0"
}

Declare event/effect routing on the parent's sealed types and fuse sub-presenters inside buildPresenter:

sealed interface MyEvent {
    @MapTo(ChildEvent.Add::class) data class AddItem(val title: String) : MyEvent
}
sealed interface MyEffect {
    @MapFrom(ChildEffect.Added::class) data class ShowAdded(val title: String) : MyEffect
}

@Composable
fun PresenterScope<ChildEvent, ChildEffect>.childPresenter(): ChildState { /* … */ }

@Composable
fun myScreenPresenter(): Presentation<MyState, MyEvent, MyEffect> = buildPresenter {
    val list = fuse { childPresenter() }
    MyState(list.items)
}

UI calls presentation.send(MyEvent.AddItem("milk")); the event is routed into the child's on<ChildEvent.Add> handler, the child emits a ChildEffect.Added, and the parent surfaces it as MyEffect.ShowAdded on presentation.effectFlow. All routing plumbing is generated by the compiler plugin — no reflection.

The runnable demo lives in demo/:

cd demo
../gradlew runJvm

Documentation

  • Getting started — motivation, what you write, full Todo example walkthrough, project layout, build commands.
  • Testing — fusio-test headless harness, scenario API, sub-presenter testing, integration with Kotest / Turbine.
  • Implementation notes — for contributors touching fusio-runtime internals.
  • docs/ — additional design notes (event-handler exhaustiveness, multi-compiler-version layer, etc.).

Compatibility

Kotlin versions

Your Kotlin Supported
2.3.0 – 2.3.x ✅
2.4.0-Beta2+ ✅

A single fusio-compiler-plugin jar ships with a per-Kotlin-version compatibility layer inside it (a shaded fusio-compiler-compat + kXXX submodule pattern inspired by ZacSweers/Metro). The plugin inspects the running Kotlin compiler at startup and ServiceLoader- resolves the matching impl, so picking up a new patch needs no Fusio release.

Platform targets

fusio-annotations, fusio-runtime, and fusio-test publish to:

Target Status
JVM ✅
Android (com.android.kotlin.multiplatform.library, minSdk 21) ✅
iOS (iosArm64, iosSimulatorArm64) ✅
macOS (macosArm64) ✅
JS (js(IR) — browser & node) ✅
Wasm (wasmJs — browser & node) ✅

commonTest runs on all of the above. watchOS, tvOS, Linux, and Windows aren't configured yet but pose no fundamental obstacle.

License

Apache License, Version 2.0 — see LICENSE.

The design of the compiler-compat layer and the plugin's apply-time Kotlin-version warning are inspired by ZacSweers/Metro (also Apache 2.0). See NOTICE.