Introducing Orbit MVI for Kotlin and Android
We at Babylon Health are delighted to introduce Orbit MVI, a library for the Model-View-Intent UI architecture pattern we’ve developed in-house and been using in production for over two years.
It gives us joy and makes our code robust, and we hope it will provide value to your project too.
What is MVI?
Very briefly — this is how a typical MVI system works.
- The UI sends actions asynchronously to some business component.
- The business component applies some business logic to the incoming actions.
- The business component then emits some events.
- The reducer combines the current state and the incoming event to produce a new state.
- The state is emitted to the UI which renders itself based upon the information within.
This behaviour introduces a pattern that is commonly called the unidirectional data flow, which very nicely sums up what MVI is. Data can only flow clockwise through components in this diagram.
This fact ensures that each component along the way has its clear cut purpose and can access only the information it needs to do its job.
The ability for a particular component to interfere with the rest of the system is removed, keeping your code SOLID.
In short, MVI reduces the possibility to make mistakes thanks to its predictable behaviour.
Orbit MVI concepts
We can map the above logic into concrete components of the Orbit library.
Note that here the UI becomes something
— this is because you can use an Orbit cycle with or without a UI view. For Android, that something
can be an Activity, Fragment or View or just a plain class.
- The source
something
sends actions to an action relay (subject). - Transformers are (optional) components that listen to specific events coming out of that relay.
- Upon receiving events they listen for, transformers apply their business logic and spit out events of their own.
- The transformer events optionally end up in a reducer.
- The reducer reduces the current state of the system with the incoming event to produce a new state.
- The new state is sent to a state relay.
something
is observing state updates and can consume the updated state.
Keeping this mapping in mind will allow you to map MVI concepts into code with Orbit easily!
Getting started with Orbit
Why would you choose our library for MVI?
- Built on top of RxJava to minimise the amount of custom code
- Easy to follow DSL for MVI, get started in just a few lines of code
- Designed for Android, but can run without any UI too!
- Predictable threading and auto unsubscription
- Proven in production for over two years at Babylon
Before we get started, be aware that Orbit currently works with RxJava 2. There are plans to make it work with coroutines-flow in the future.
To have a functioning MVI system for a ViewModel or Activity, we need to do some simple setup.
Adding the dependency
Check out our Orbit MVI repository on GitHub to find out the latest version.
Add these two lines to your build.gradle
for your application.
Creating the ViewModel
OrbitViewModel
is the most important class in this library. It wraps the description of your MVI flows into a container that provides you with one input for your actions and two outputs — one output for your state, the other for your side effects, e.g. navigation.
That’s it! The ViewModel does nothing at this point, but this is theoretically a fully functional MVI system.
Injecting the ViewModel into the Activity
You will have to inject the ViewModel to a Fragment or Activity to perform any useful functions. The scalable way to do this is use a framework like Koin or Dagger.
For more details on how to do it for your ViewModels refer to these articles:
- Architecture Components with Koin: ViewModel
- ViewModel with Dagger2 (Android Architecture Components)
Connecting the Activity to the ViewModel
Preparing the render method and then adding one line of code in onStart
is all we need to do to connect our ViewModel!
Orbit takes care of unsubscribing the view from updates in the lifecycle method symmetrical to the one you’ve called connect
in. Examples:
onCreate → onDestroy
onStart → onStop
onResume → onPause
We’ve just set up a fully functional MVI system on an Android Activity with just a few lines of code! If you squint, it’s 4 lines of code of setup :)
Our first MVI flow
Let’s put our new MVI system to good use! Let’s start by adding a simple action that increments the counter on our screen state.
Simple right? We add a flow in our ViewModel that reacts to the new IncrementAction
and increments the counter in the state.
Now we need to handle that information in our Activity:
This code is enough to get you started :) However, there is much more to Orbit than this!
Explore our repository for more complete examples and read up more about our DSL syntax.
What’s next for Orbit
We have a whole host of improvements planned for Orbit coming soon, including, in no particular order:
- Improve logging and error handling
- Add more tests and unit testing guidelines
- Make separate MVI systems easier to compose
- Improve Android support even more
- Add coroutine flow support
Suffice to say, we firmly believe in MVI as an architecture pattern and are committed to making this library continually better.
In a follow-up article, we will present what migrating from MVP towards MVI can give you, and your teammates joy and make your code more robust.
Links
Here are some recommended links if you wish to learn more about MVI:
Mikolaj works as an Android Engineer at Babylon Health. If you would like to join him building the future of healthcare apply here.