Streams
A lightweight reactive streams library for Kotlin Multiplatform. Every stream always has a value.
Reactive state shouldn't be complicated
Reactive programming is a genuinely good idea: instead of manually updating every place that depends on some value, you declare the dependency and let changes propagate automatically. But many reactive libraries were designed for event streams and async sequences — problems where values arrive over time and might never arrive at all. For UI state, that model adds ceremony you don't need.
The core idea: streams always have a value
Most reactive libraries model streams as sequences that might eventually emit something. You subscribe,
wait, and handle the "not yet" state with operators like startWith, defaultIfEmpty, or
onErrorResumeNext.
Streams takes a simpler approach: every stream always has a current value. You can read it at
any time with stream(). When you subscribe, you immediately get the current value.
val source = StreamSource(42)
// Always has a value — read it anytime
println(source()) // 42
// Subscribe — immediately called with current value
source.subscribeToStream { value ->
println("Got: $value")
}
// prints "Got: 42" immediately
// Update — all subscribers notified
source(99)
// prints "Got: 99" This works well for UI programming. In Kraft, components subscribe to streams and re-render when values change. Because streams always have a value, the first render always has data.
Two types
The entire library is built on two types:
Stream<T>— read-only. Hasinvoke(): T(get value) andsubscribeToStream()(observe changes).StreamSource<T>— read-write. Addsinvoke(next: T)(set value),modify(), andreset().
Operators transform a Stream into another Stream. The chain is lazy — operators only subscribe upstream when they have downstream subscribers.
What you get
Always Has a Value
stream() never blocks, never returns null (unless T is nullable).
Composable Operators
map, filter, combine, debounce, fold, distinct, history, and more.
Lazy Subscriptions
Operators only subscribe upstream when they have downstream subscribers.
Kotlin Multiplatform
Core + all operators work on JVM and JS. Plus JS-specific extras like animTicker() and localStorage persistence.
Resilient
A failing subscriber never prevents other subscribers from being notified. Streams keep flowing.
Flow Interop
stream.asFlow() bridges into Kotlin's Flow world when you need it.
Quick taste
val name = StreamSource("World")
val greeting = name.map { "Hello, $it!" }
greeting.subscribeToStream { println(it) }
// prints "Hello, World!"
name("Kotlin")
// prints "Hello, Kotlin!"
// Combine two streams
val firstName = StreamSource("Jane")
val lastName = StreamSource("Doe")
val fullName = firstName.pairedWith(lastName).map { (f, l) -> "$f $l" }
println(fullName()) // "Jane Doe"
firstName("John")
println(fullName()) // "John Doe"