Skip to content

lamphama2/mvi_base

Repository files navigation

mvi_base

Base classes for MVI architecture

This model is inspired by Redux and Flux models. Some core actors stay the same as in Redux but their behaviors might change to adapt to the requirements of Android's environnement. The idea, similarly to Flux's original purpose, revolves around a strict unidirectional data flow.

Image of diagram class

The main actors in the model are:

  • View: this is the starting point of a flow. It dispatches an Action to Store. When Store sends back output, View updates UI within render() method. This method is also the endpoint of the flow. Thus, a complete and close flow is always starting from a dispatch() and finishing with render(). In this part, there are 2 smaller elements, View and ViewModel. Unlike in MVVM model, ViewModel here is no more than a state manager (thanks to the principle of ViewModel from Jetpack) for View. It does not contain any logic business.

    • Codes in ViewModel are less (or even not) Android than in View; it only helps to hold and to mutate states.

    • Typically, View performs all actions related to UI. On top of that, it helps to bind data from ViewModel to UI

    • Relation between View - ViewModel is : n..1 (n..n can also work, sometimes a view needs more than 1 ViewModel to manage its states; however it's not really necessary to do that).

  • Action : this defines what will happen. Unlike in Redux, Action carries and transfers the current state of view

  • Store : unlike Store in redux, this Store does not hold states of application, but instead acts as:

    • a singleton

    • a Reducer carrier

    • a communication point between View components (fragments, activity)

  • Reducer : in redux, Reducer forms an object with a unique function which is reduce(). However, in this model, object Reducer no longer exists but its magic method reduce() is integrated directly in Store. This method is a pure-function that takes Action as input and returns State. This is where all core logics happen. Hence, to keep it maintainable and testable, there are certain things that should never happen in reduce() function:

    • Mutate the arguments

    • Perform side effect actions.

  • State: this is an updated state container that will be sent to View to render()

Structure of base classes

To keep a certain abstraction level, some base classes are created to perform the relation between components in the architecture.

  • BaseView (BaseFragment, BaseActivity): Each View is defined with 3 dependencies: type of Action, type of State and type Store. As a typical element in View part, BaseView provides dispatch() and render() methods. It contains a Store reference and possibly a corresponding ViewModel one (or shareViewModel), if state management is required. This ViewModel reference can be bound to xml layout and benefits from DataBinding.

  • BaseViewModel: this is a Lifecycle observer because its lifecycle depends on the lifecyle of the View it is attached to. Since this component is a part in the big View, BaseViewModel also provides dispatch() and render() and holds a reference of Store. On top of that, it may (or should because purpose of its existence is to manage states of View) contain states.

  • render(): this method should not be called outside of a flow. States will not be changed without an action dispatched.

  • BaseStore: except some core elements like reduce() method mentionned above, BaseStore contains a Dispatcher which does some pre-setup (things like providing executor for action) for every action dispatched. Moreover, BaseStore provides methods to subscribe and unsubscribe to state changes. BaseStore is a singleton at module level.

* I'm not sure whether "module" describes exactly what I meant. However, imagine a process that can work independently. For instance, in my projects, there are different processes: authentification (login/register/password reset), main flow, payment, etc. Since each module contains only one Activity, the Store depends on the lifecycle of that Activity.

  • reduce(): since I'm currently using RxJava for threading and reactivity handling, reduce() acts as chain of Rx operators starting from an Observable<Action> and returning an Observable<State>

  • Dispatcher: it filters the action received from View and prepares environment for the execution.

Flow (what you have to do after having all base classes ready)

Image of diagram flow

To start a flow:

  • Describe what you want to do by defining an Action class with required data.

  • From either BaseView or BaseViewModel, dispatch() that action.

  • Handle action in reduce() method of Store while defining an State returned.

  • Update UI in render() View or update state in render() of ViewModel.

  • And then, you are good.

Analyse

Avantages

  • This model benefits from unidirectionnal flow:

    • Easier to debug because at any point of a flow, you know where it comes from
    • System of Action/State helps you have more control over your data
  • Core logics of application are situated in Store within reduce() method, and the fact that this is a pure-function makes it obviously easy to test.

  • Communication between components is done with the helps of Publish/Subscribe pattern and abstraction layer, it makes the system loosely coupled and avoids mixing Android framework codes with Java codes

Disadvantages

  • Many classes are generated (Action/State) when you want to execute a flow. It will get harder to manage Action and State classes when system becomes massive

About

Base classes for MVI architecture

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages