Orbit Multiplatform wins Kotlin Foundation Grant: A Journey and a Look Ahead
We’re thrilled to announce that the Kotlin Foundation has awarded Orbit Multiplatform a grant! This recognition is a tremendous honour and a chance to reflect on Orbit’s journey and share our vision for the future.
Orbit’s Origins
Orbit began as an open-source project over five years ago, in September 2019. It was one of the first libraries I co-created with Mikołaj Leszczyński, and its development has been a journey of constant learning and evolution.
The Early Days: Orbit 1
I first encountered Orbit when I joined the Android team at Babylon Health in July 2018. Mikołaj had already developed the initial version of Orbit, heavily inspired by RxJava and talks such as Jake Wharton’s Managing The Reactive World with RxJava. RxJava’s influence was evident in Orbit’s reactive approach, with chained operations driving the architecture.
However, while this design worked well in some cases, it posed significant challenges — especially with debugging. For instance, one event could trigger multiple others, leading to hard-to-trace bugs. Additionally, we ran into issues with type erasure due to the use of generics, which made it even harder to maintain the library.
Here’s an example of the syntax from Orbit 1:
data class State(val total: Int = 0)
data class AddAction(val number: Int)
class CalculatorMiddleware: Middleware<State, Unit> by middleware(State(), {
perform("addition")
.on<AddAction>()
.withReducer { state, action ->
state.copy(state.total + action.number)
}
})
While the initial approach was a valuable learning experience, it became clear that Orbit needed a shift.
The Transition: Kotlin Coroutines and Orbit 2
In October 2018, Kotlin 1.3 introduced coroutines, offering an opportunity to rethink Orbit’s architecture. Coroutines brought structured concurrency to Kotlin, making asynchronous programming more straightforward and less error-prone. However, it wasn’t until May 2020 that we began redesigning Orbit’s syntax to leverage coroutines. By August 2020, we released Orbit 2.
We introduced the ContainerHost
class, moving from inheritance to composition and simplifying event handling with direct function calls. For example:
class CalculatorViewModel: ContainerHost<CalculatorState, CalculatorSideEffect>, ViewModel() {
// Include `orbit-viewmodel` for the factory function
override val container = container<CalculatorState, CalculatorSideEffect>(CalculatorState())
fun add(number: Int) = orbit {
sideEffect {
post(CalculatorSideEffect.Toast("Adding $number to ${state.total}!"))
}
.reduce {
state.copy(total = state.total + number)
}
}
fun subtract(number: Int) = orbit {
sideEffect {
post(CalculatorSideEffect.Toast("Subtracting $number from ${state.total}!"))
}
.reduce {
state.copy(total = state.total - number)
}
}
}
Simplified Syntax
We still weren’t entirely satisfied, and two months later, in October 2020, we released a simplified DSL that cleaned up the syntax and made Orbit more user-friendly:
class CalculatorViewModel: ContainerHost<CalculatorState, CalculatorSideEffect>, ViewModel() {
// Include `orbit-viewmodel` for the factory function
override val container = container<CalculatorState, CalculatorSideEffect>(CalculatorState())
fun add(number: Int) = intent {
postSideEffect(CalculatorSideEffect.Toast("Adding $number to ${state.total}!"))
reduce {
state.copy(total = state.total + number)
}
}
}
This shift toward simplicity also required us to implement a plugin architecture, allowing the core of Orbit to remain independent of the syntax. While this added some initial complexity, we were able to refine it in future releases.
Orbit’s Independence and Community Support
Although we left Babylon Health by the end of 2020, Orbit continued to grow. Since becoming independent of our employer, we’ve released over 30 versions, largely thanks to the contributions of our incredible community. Whether it’s new features, bug fixes, or ideas, the support from users and contributors has been invaluable.
In early 2021, we added initial support for Kotlin Multiplatform. However, one of the most important recent additions has been improvements to Orbit’s testing framework, inspired by Turbine. These improvements have made testing more robust and easier to work with.
Here’s an example test case using this framework:
@Test
fun exampleTest() = runTest {
ExampleViewModel().test(this) {
expectInitialState()
runOnCreate()
containerHost.countToFour()
expectState { copy(count = 1) }
expectSideEffect(Toast(1))
expectState { copy(count = 2) }
expectSideEffect(Toast(2))
expectState { copy(count = 3) }
expectSideEffect(Toast(3))
expectState { copy(count = 4) }
expectSideEffect(Toast(4))
}
}
The Future of Orbit: What’s Next?
Receiving the Kotlin Foundation Grant is a significant milestone for Orbit, allowing us to focus on its future with renewed energy. Our grant proposal includes several exciting initiatives:
- First and foremost, provide better guidance around Multiplatform use and investigate improvements in this area, such as lifecycle management, state persistence
- Updating project to take advantage of Kotlin 2.x features, such as context parameters
- Adding support for Desktop platforms (MacOS, Linux, Windows)
- Adding integration with Compose Multiplatform
- While we are being held back by the single-threaded concurrency model in JS and WASM, we will continue exploring whether we can bring Orbit to these platforms in a way that still makes sense
- Improve the documentation with more practical examples, best practices and tips
- Add Logging and Time Travel debugging features
Gratitude and Acknowledgment
While the grant offers financial support, what truly matters to us is the recognition that Orbit Multiplatform is making a meaningful impact on the Kotlin community. We’re incredibly grateful to all the developers who use Orbit, especially our contributors who have helped shape the library over the years.
We’d love to hear from you if you want to join our community! You can find us in the Kotlin Slack channel under #orbit-mvi.
Thank you for participating in this journey — we’re excited about the future and can’t wait to see where Orbit goes next.
Join medium to read all of my articles or subscribe for e-mail updates.