Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
This repository was archived by the owner on Apr 19, 2022. It is now read-only.

MVI architecture Implementation of the ToDo app.

License

NotificationsYou must be signed in to change notification settings

oldergod/android-architecture

 
 

Repository files navigation

Java Version

You can find the Java version of this apphere.

Contributors

Benoît Quenaudon

Summary

This version of the app is called TODO-MVI-RxJava-Kotlin. It 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 is initially a fork of theTODO-MVP-RXJAVA.

The MVI architecture embraces reactive and functional programming. The two main components of this architecture, theView and theViewModel can be seen as functions, taking an input and emiting outputs to each other. TheView takes input from theViewModel and emit backintents. TheViewModel takes input from theView and emit backview states. This means theView has only one entry point to forward data to theViewModel and vice-versa, theViewModel only has one way to pass information to theView.
This is reflected in their API. For instance, TheView has only two exposed methods:

interfaceMviView {funintents():Observable<MviIntent>funrender(state:MviViewState)}

AView will a) emit its intents to aViewModel, and b) subscribes to thisViewModel in order to receivestates needed to render its own UI.

AViewModel exposes only two methods as well:

interfaceMviViewModel {funprocessIntents(intents:Observable<MviIntent>)funstates():Observable<MviViewState>}

AViewModel will a) process theintents of theView, and b) emit aview state back so theView can reflect the change, if any.

View and ViewModel are simple functions.

The User is a function

The MVI architecture sees the user as part of the data flow, a functional component taking input from the previous one and emitting event to the next. The user receives an input―the screen from the application―and outputs 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.

Model-View-Intent architecture in details

MVI in details

We saw what theView and theViewModel were designed for, let's see every part of the data flow in details.

Intent

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.

Action from Intent

Intents are in this step translated into their respecting logicAction. For instance, inside the tasks module, the "opening the view" intent translates into "refresh the cache and load the data". Theintent and the translatedaction 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 sameaction for multiple differentintents.

Action

Actions defines the logic that should be executed by theProcessor.

Processor

Processor simply executes anAction. Inside theViewModel, this is the only place where side-effects should happen: data writing, data reading, etc.

Result

Results are the result of what have been executed inside the Processor. Their can be errors, successful execution, or "currently running" result, etc.

Reducer

TheReducer is responsible to generate theViewState which the View will use to render itself. TheView should be stateless in the sense that theViewState should be sufficient for the rendering. TheReducer takes the latestViewState available, apply the latestResult to it and return a whole newViewState.

ViewState

TheState contains all the information theView needs to render itself.

Observable

RxJava2 is used in this sample. The data model layer exposes RxJavaObservable streams as a way of retrieving tasks. In addition, when needed,void returning setter methods expose RxJavaCompletable streams to allow composition inside theViewModel.
Observable is used overFlowable because backpressure is not (and doesn't need to be in this project) handled.

TheTasksDataSource interface contains methods like:

fungetTasks():Single<List<Task>>fungetTask(taskId:String):Single<Task>funcompleteTask(task:Task):Completable

This is implemented inTasksLocalDataSource with the help ofSqlBrite. The result of queries to the database being easily exposed as streams of data.

overridefungetTasks():Single<List<Task>> {...return databaseHelper.createQuery(TaskEntry.TABLE_NAME, sql)            .mapToList(taskMapperFunction)            .firstOrError();}

Threading

Handling of the working threads is done with the help of RxJava'sSchedulers. For example, the creation of the database together with all the database queries is happening on the IO thread.

Immutability

Data immutability is embraced to help keeping the logic simple. Immutability means that we do not need to manage data being mutated in other methods, in other threads, etc; because we are sure the data cannot change. Data immutability is implemented with Kotlin'sdata class.

Functional Programming

Threading and data mutability is one easy way to shoot oneself in the foot. In this sample, pure functions are used as much as possible. Once anIntent is emitted by theView, up until theViewModel actually access the repository, 1) all objects are immutable, and 2) all methods are pure (side-effect free and idempotent). The same goes on the way back. Side effects should be restrained as much as possible.

ViewModel LifeCycle

TheViewModel should outlive theView on configuration changes. For instance, on rotation, theActivity gets destroyed and recreated but yourViewModel should not be affected by this. If theViewModel was to be recreated as well, all the ongoing tasks and cached latestViewState would be lost.
We use theArchitecture Components library to instantiate ourViewModel in order to easily have its lifecycle correctly managed.

Dependencies

Features

Complexity - understandability

Use of architectural frameworks/libraries/tools:

Building an app following the MVI architecture is not trivial as it uses new concepts from reactive and functional programming.

Conceptual complexity

Developers need to be familiar with the observable pattern and functional programming.

Testability

Unit testing

Very High. The ViewModel is totally decoupled from the View and so can be tested right on the jvm. Also, given that the RxJavaObservables are highly unit testable, unit tests are easy to implement.

UI testing

Similar with TODO-MVP. There is actually no addition, nor change compared to the TODO-MVP sample. There is only some deletion of obsolete methods that were used by the ViewModel to communicate with the View.

Code metrics

Compared to TODO-MVP, new classes were added for 1) setting the interfaces to help writing the MVI architecture and its components, 2) providing the ViewModel instances via theViewModelFactory, and 3) handling theSchedulers that provide the working threads. This amount of code is actually one big downside of this architecture but can easily be tackled by usingKotlin.

-------------------------------------------------------------------------------Language                     files          blank        comment           code-------------------------------------------------------------------------------Kotlin                          75            886           1645           3977 (3639 in MVP-RXJAVA,  (4798 in MVI-RXJAVA-KOTLIN))XML                             34             97            338            610-------------------------------------------------------------------------------SUM:                           109            983           1983           4587-------------------------------------------------------------------------------

Maintainability

Ease of amending or adding a feature

High. Side effects are restrained and since every part of the architecture has a well defined purpose, adding a feature is only a matter of creating a new isolated processor and plug it into the existing stream.

Learning cost

Medium as reactive and functional programming, as well as Observables are not trivial.

About

MVI architecture Implementation of the ToDo app.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Kotlin100.0%

[8]ページ先頭

©2009-2025 Movatter.jp