The goal of this sample app is to show how we can implement unidirectional data flow architecture based on Flux and Redux on Android... using Kotlin :wink:
A sample app that shows how we can apply unidirectional data flow architecture on Android using Kotlin.
This app comes up as a support material for my talk Unidirectional data flow architecture on Android using Kotlin.
Talking about architecture is always tough, so as for support for the talk and slides that I created, I decided to create an app where I show what I explain in the talk/slides, so is easier to understand everything; and getting your hands dirty is always fun!
The slides of the talk can be found here.
I've written a blog post series explaining the details of this architecture:
Our approach is based on Redux and Flux from Facebook (that are pretty popular unidirectional data flow approaches on Web), but slightly different, taking things from each one to work on the way we want.
Why the K ? because we use Kotlin as the programming language, apart from being such a nice and enjoyable language, we take advantage of the really great features this language provide us for building our app.
I wanted to keep this app simple enough so we can understand what we want to learn and is important here, that is just unidirectional data flow. Adding libraries that can help us but have their own principles and learning curve could distract us from our real problem here and make, probably, much more difficult to understand it, so for that reason I decided to go without other libraries and implement everything just using Kotlin code; of course we have kotlin libraries that helps us to work with Kotlin on Android, and test libraries to help us when testing, but apart of that, no libraries, just pure Kotlin code :blush:
For a much deeper and better understanding of what we are going to explain here with this document and the code, I deeply encourage you to read the official documentation of Flux and Redux before.
This app is a simple Items list app, where we can create, edit and delete Items. Items are shown in a list where they can be reordered, deleted and set as favorites. In the edit screen, we can create and edit Items, we can change text and color.
Ok, this is a simple app what is the magic here? Every action in the app follows an unidirectional data flow.
When we click on the button to add a new Item and we navigate to the edit item, when we change the Item's color, when we update the item, when we reorder items, etc. everything follows an unidirectional data flow.
Sure! here you have some screenshots that show basically what we can do with the app:
(You can also reorder items but I was not able to take a screenshot during the process :innocent:)
Yes we do, I work in a small team inside One Note at Microsoft and we are following this architecture on the project that I'm working right now on all platforms :smile:
If you are reading this probably you make software, independently of the platform, you make software.
When has been the last time that you have been working on a multi-platform product, with different teams that work on each platform dealing each one of you with the same features, same problems,.. and then.. going to a meeting, talking to a colleague from other team about how they do x and realise that:
We know it, we know that pain, how it feels, and we wanted to avoid that if possible; we wanted:
Having the same architecture on all our platforms, make our talks between clients amazing, we all are in the same boat, we know how to drive that boat and how to improve it, all of us.
And of course, we take advantage of all of the benefits of an unidirectional data flow architecture, but here the team from Redux explain it much better :wink:
Nope, this code is inspired by what we do and how we do, but is much simpler, so that's better for educational purposes like we have here :wink:
I think an image is more valid than 1000 words:
This app has three modules and three main components:
App is the module where our views live, it has the components that the user interacts with.
Also contains ControllerView that acts between views and everything else.
If you are used to MVP pattern, they are very similar to presenters, so they help us to decouple the logic of our business from the view/framework.
We are going to see later the other different components, but basically what the ControllerView does is being subscribed to State changes, creates an Action after the user interacts with the View (the View has a ControllerView), dispatches the action, and later, when it receives a new state, communicate back to its view so the view can react to the new state and render itself.
Store is the main module of our app, our business logic resides there.
If you are coming from Clean Architecture, Store is our Domain. The store has several and important components:
Persistence is a side effect. Ok, first, what a side effect is?
Side effects are optional and are used to enrich and extend the functionality of our app. For instance, in our case, the store and the state live in the memory, the data is never persisted physically, nor in a DB nor in a file, etc.
How can add persistence to our architecture? the response is side effects. Side effects add extra functionality, like in our case persistence in a DB.
A side effect runs after the main operation has been done, in our case, a side effect is a process that is going to run after our store has reduced an action together with the current state. Once that the store has reduced the action, is going to dispatch that action (the same) to its side effects (that are subscribed to it) if any.
Side effects will run completely separated and isolated from each other and from the rest of the world.
Side effects once that have finished handling the Action, can dispatch a new and a different action to the store, then the store will reduce that new action together with the current state.
For instance when we fetch data from the DB, is the perfect scenario where we send a new action with the new items that we have fetched.
A pretty cool thing of side effects is that we can "enable or disable" them, as you can see in this video that I've uploaded where we enable and disable the persistence side effect and the app works without any problem (of course when is disabled we don't have DB persistence). This is because, at the end, a side effect is subscribed to handle an action after the store has reduced it, if is not subscribed to the store then it won't handle actions, but that's all, our app should be able to work without it (of course, in many cases, side effects are essential, think in a DB, a sync layer, etc.).
Btw in this sample app we use Realm as DB, mainly because I wanted to experiment with it and because is really easy to build a small DB with a simple scheme and operations, but we could replace this DB for another really easily (the good thing about having everything decoupled).
Threading is really important in Android, we never want to block the UI thread when we do other stuff that is not UI related, we always want to give the best possible user experience to our users so what we do is:
Of course when testing, dealing with threads is a nightmare, so we have mechanisms in the app to execute everything in the main thread in that case (DI FTW).
We want to have a good architecture that its components are decoupled from each other, so we can have our business logic completely independent from frameworks, we can test each component in an isolated way, we can unit test without the need of work on instrumentation tests that are slow, etc.
This is key in Clean architecture, and this approach (and Redux and Flux) follow that too. Of course, you can mess everything, but if you want (and you should), you can have your components decoupled and isolated so they are easier to maintain, to test, to extend, etc.
A diagram of the dependencies of the different components of our architecture is:
As we can see:
Now that we know the dependencies of our different components we can say how we can test them:
Of course, we have, this is not the Philosopher's Stone of the architectures, and as any other architecture, we have issues, difficult things to do and to improve, problems to solve, etc.
For instance, a list of things that we have not covered here (nor in the slides) but are really important:
We already have referenced to useful links and documentation through the whole document, but here is a list of really awesome and useful references that you can have a look to understand even better, what I've tried to explain here, with the talk, the slides, and the code:
I've also uploaded and created some videos that are used in the slides of the presentation to help to understand what is happening under the hood when we interact with the app:
This is an open source sample app released under Apache License v2, so it means that you can take the code an use it in your open source projects that use compatible licenses with Apache, but also in your privative ones.
This license requires attribution, so if you are going to use the code, respect the attribution, respect the class headers that mention the license terms and author names, and if you are going to release your app (that uses this code) to the world (the binary, the apk) then create a section in your app where you mention all the libraries your app uses including author names, code licenses, and code source links.
Even when this code is not a Microsoft product, we have adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact us with any additional questions or comments.