This app is very much a work in progress and subject to change. At the time of writing this its nothing more than a TextView which displays the price (hopefully large) of magic internet money.
App which attempts to use the latest cutting edge libraries and tools. As a summary:
- Entirely written in Kotlin
- Unidirectional data flow (fully reactive architecture)
- Uses RxJava 2
- Uses RxAndroid and RxKotlin Extensions
- Uses all of the Architecture Components: Room, LiveData and Lifecycle-components
- Uses dagger-android for dependency injection
- Uses Glide for image loading
- Uses Groupie for any complex RecyclerView layouts with Kotlin support
First off, you require the latest Android Studio 3.0 (or newer) to be able to build the app.
This project uses ktlint, provided via the spotless gradle plugin, and the bundled project IntelliJ codestyle.
If you find that one of your pull reviews does not pass the CI server check due to a code style conflict, you can
easily fix it by running: ./gradlew spotlessApply
, or running IntelliJ/Android Studio's code formatter.
The architecture used in this app is based on an Android ported version of the Model-View-Intent architecture and uses RxJava to implement the reactive characteristic of the architecture. It takes learnings from:
The MVI architecture embraces reactive and functional programming. The two main components of this architecture, the View and the ViewModel can be seen as functions, taking an input and emiting outputs to each other. The View takes input from the ViewModel and emit back intents. The ViewModel takes input from the View and emit back view states. This means the View has only one entry point to forward data to the ViewModel and vice-versa, the ViewModel only has one way to pass information to the View.
This is reflected in their API. For instance, The View has only two exposed methods:
interface MviView {
fun intents(): Observable<MviIntent>
fun render(state: MviViewState)
}
A View will a) emit its intents to a ViewModel, and b) subscribes to this ViewModel in order to receive states needed to render its own UI.
A ViewModel exposes only two methods as well:
interface MviViewModel {
fun processIntents(intents: Observable<MviIntent>)
fun states(): Observable<MviViewState>
}
A ViewModel will a) process the intents of the View, and b) emit a view state back so the View can reflect the change, if any.
The MVI architecture sees the user as part of the data flow, a functionnal component taking input from the previous one and emitting event to the next. The user receives an input―the screen from the application―and ouputs back events (touch, click, scroll...). On Android, the input/output of the UI is at the same place; either physically as everything goes through the screen or in the program: I/O inside the activity or the fragment. Including the User to seperate the input of the view from its output helps keeping the code healty.
We saw what the View and the ViewModel were designed for, let's see every part of the data flow in details.
Intents represents, as their name goes, intents from the user, this goes from opening the screen, clicking a button, or reaching the bottom of a scrollable list.
Intents are in this step translated into their respecting logic Action. For instance, inside the tasks module, the "opening the view" intent translates into "refresh the cache and load the data". The intent and the translated action are often similar but this is important to avoid the data flow to be too coupled with the UI. It also allows reuse of the same action for multiple different intents.
Actions defines the logic that should be executed by the Processor.
Processor simply executes an Action. Inside the ViewModel, this is the only place where side-effects should happen: data writing, data reading, etc.
Results are the result of what have been executed inside the Processor. Their can be errors, successful execution, or "currently running" result, etc.
The Reducer is responsible to generate the ViewState which the View will use to render itself. The View should be stateless in the sense that the ViewState should be sufficient for the rendering. The Reducer takes the latest ViewState available, apply the latest Result to it and return a whole new ViewState.
The State contains all the information the View needs to render itself.