Just add MVI with Orbit 2
We love the MVI architecture concept.
It brings clarity and consistency to our codebases. We wish to share our best knowledge of simple, no-nonsense MVI for Kotlin, Android and beyond. It fits both small and large projects well. We hope you find the library we created useful to you and makes your life a bit easier.
We spent the last few months building a much-improved version of our MVI library, Orbit 2, taking the best features of Orbit 1 and rewriting the rest from scratch.
Let’s jump straight into the action to show you how easy it is to build MVI into your application. We will load a list of posts to form a Twitter-like feed.
Creating an Orbit ViewModel
First we include the Orbit library in Gradle:
To get an Orbit-enabled ViewModel
, implement the ContainerHost
interface. Then, use one of our handy factory functions to create the Orbit container.
Now let’s load some data.
Loading data on container creation
A typical use case on Android would be to run an initial request to load data when you create the container for the first time. Orbit supports this use case through a lambda you pass to the container factory function:
After setting that up, we can invoke Orbit to launch a network request.
Let’s unpack what happened here. We can distinguish Orbit operators used:
- The
orbit
block allows you to build and run MVI flows when you call a function. - The
transform
operator allows you to map upstream events. In this case, there is no upstream, so all we do is make a blocking network call. All transform operators execute on an IO thread pool. - The
reduce
operator reduces upstream events with the current state of the container. The result is a new container state.
We can optimise this further but for now, let’s connect our UI to the ViewModel
.
Connecting the ViewModel to Android UI
The easiest way to connect our ViewModel
is via Flow
.
Invoking actions on our MVI system from the UI is easy - just call a function on your ViewModel
. As an example, we will make the pull to refresh gesture load the overviews again:
Your ViewModel
functions are plain old functions, so you can pass parameters through if needed as normal. Your MVI flows can then use these parameters.
Optimising the ViewModel
Blocking calls, even offloaded to a background thread, are not the best practice. Fortunately, the Orbit library is extensible and modular. We provide extra operators via modules.
Orbit 2 design
We have big ambitions for Orbit. Yet, the design decisions we made when we first wrote what we now call Orbit 1 held us back from achieving our goals. Above all, we value simplicity and developer experience. While devs loved working with Orbit 1, we felt we could simplify it further. Testing was also another area of improvement due to it being quite cumbersome.
Times have moved on. Coroutines are now officially recommended by Google for asynchronicity in Android. We just had to support them in Orbit. At the same time, we did not want to lose RxJava compatibility. The old architecture meant having both was not possible.
One fundamental shift in Orbit 2 was doing away with using streams for everything. We can represent the MVI concept via one big reactive cycle. In that cycle, network requests are mid-cycle transformations. The suggestion for RxJava MVI implementations was to use ObservableTransformers. That is, in our opinion, overcomplicated. The reality is that 95% of use cases only ever emit one result. For this, you do not need streams. Why make 100% of your code complex to support 5% of corner cases?
The most common use case is that one UI action sets in motion a one-off MVI flow (e.g. a network request). Orbit 2 makes that use case the primary focus to inform the developer experience. Worry not, we still allow you to connect streams where you need them.
The result is a clear and concise syntax with 1–1 mapping between UI actions and MVI flows.
MVI architecture can describe complex systems in simple terms. Indeed, we don’t think we should limit its usefulness to UI. The platform you use it on should not be a limitation either.
We currently support only Kotlin and Android projects. The JetBrains team has put hard work into making Coroutines multiplatform. That enables us to consider taking Orbit to other platforms.
All the pain points above guided us to Kotlin Coroutines as the basis for Orbit 2.
Wrapping up
We hope we piqued your interest and you found this post informative. That was just a taste of Orbit’s capabilities!
Orbit 2 in a nutshell:
- Integrates best practices from our 2+ years of experience with MVI
- Powered by Coroutines
- Easy to use, type-safe, extensible API
Works with any async/stream framework
Orbit ❤️ Android
- Subscribe to state and side effects through Flow
- ViewModel support, along with SavedState
Testing
- Unit test framework designed in step with the framework
- Built-in Espresso idling resource support
Documentation and more complete samples are available below