{"id":373,"date":"2019-05-11T13:50:58","date_gmt":"2019-05-11T12:50:58","guid":{"rendered":"https:\/\/codizon.com\/?p=373"},"modified":"2021-04-14T22:15:03","modified_gmt":"2021-04-14T21:15:03","slug":"unidirectional-flow-for-beginners","status":"publish","type":"post","link":"https:\/\/codizon.com\/2019\/05\/unidirectional-flow-for-beginners\/","title":{"rendered":"Unidirectional Data Flow for Newbies"},"content":{"rendered":"\n

Unidirectional Data Flow<\/em> is a buzz phrase that thrilled development conferences all around the world. There are plenty of frameworks that now use this concept such as State Machines<\/a>, Redux<\/a>, Flux<\/a>, Bloc<\/a>, Mobx<\/a>, among others. These are widely used on all frontend platforms (mobile, web, desktop applications, etc.) regardless of programming language or framework.<\/p>\n\n\n\n

Key Concepts<\/h2>\n\n\n\n

First, what does “unidirectional<\/em>” mean? In simple words, it means that data only has one way to be transferred to other parts of an application (i.e., a single input leads to a single output). Have you ever encountered issues like concurrency modification exceptions or race conditions? These can all be tackled with a unidirectional flow approach since only one function and one thread can make changes at a time.<\/p>\n\n\n\n

Events<\/em> (or “actions<\/em>“) describe mouse clicks, network call completions, input changes, etc. All are added to the FIFO queue.<\/p>\n\n\n\n

\"\"<\/figure><\/div>\n\n\n\n

A worker thread takes the last state and last event in the queue, and returns a new state.<\/p>\n\n\n\n

\"\"<\/figure><\/div>\n\n\n\n

The transition between states is often called a “reducer”, a “reduce function”, or a “transition”.<\/p>\n\n\n\n

\"\"<\/figure><\/div>\n\n\n\n

The transition should be a blocking function and quick. If it cannot be synchronous, consider running a task in the background and emit a new event when the task starts, finishes, or fails. This type of background task is often called a “side effect” or “middleware”.<\/p>\n\n\n\n

\"\"<\/figure><\/div>\n\n\n\n

An interface (or a “view”) not only adds events to the queue, but also observes the current state and updates itself accordingly. This approach works in a cycle, as shown in the figure below.<\/p>\n\n\n\n

\"\"<\/figure><\/div>\n\n\n\n

Further details will be provided in the following sections.<\/p>\n\n\n\n

State<\/h3>\n\n\n\n

In general, a state is a structure (class or enumeration) that holds the current status of an application or UI (i.e., components visibility, input values, etc.). To avoid ambiguous modifications, the state should always be immutable – no function or object should be able to modify the values of its fields. Below is an example of immutability.<\/p>\n\n\n\n

Mutable classes can be modified at any time:<\/p>\n\n\n\n

data class MyMutableClass(\n  var field: String \/\/ variable\n)\n\nval instance = MyMutableClass(\"hello\")\ninstance.field = \"world\"\nprint(instance.field) \/\/ outputs \"world\"<\/code><\/pre>\n\n\n\n

However, modifications to immutable classes are forbidden by the compiler or result in a runtime exception:<\/p>\n\n\n\n

data class MyImmutableClass(\n  val field: String \/\/ constant\n)\n\nval instance = MyImmutableClass(\"hello\")\ninstance.field = \"world\" \/\/ error!<\/code><\/pre>\n\n\n\n

Note: You should also ensure that collection fields are immutable.<\/p><\/blockquote>\n\n\n\n

So how then do you reflect the change? The answer is simple: create a new object or make a copy of the existing one. Ideally, check if your programming language supports a copy<\/strong><\/code> operator.<\/p>\n\n\n\n

A “new state” can be created with a copy of the existing one with some of its fields changed.<\/p>\n\n\n\n

data class State(val text: String)\n\nval instance = State(\"hello\")\nval newInstance = instance.copy(text = \"world\")\nprint(newInstance) \/\/ outputs \"State(text=world)\"<\/code><\/pre>\n\n\n\n

Normally, a state class contains a number of fields:<\/p>\n\n\n\n

data class State(\n  val id: String,\n  val isLoading: Boolean,\n  val data: String?\n)<\/code><\/pre>\n\n\n\n

Alternatively, you can use an interface:<\/p>\n\n\n\n

interface State\n\nclass Loading(val id: String) : State\ndata class Ready(val id: String, val text: String) : State\n\nval instance = Loading(id = \"abc\")\nval newInstance = Ready(id = instance.id, text = \"hello world\")\nprint(newInstance) \/\/ outputs \"Ready(id=abc, text=world)\"<\/code><\/pre>\n\n\n\n

Your UI can then observe the current state and update itself to reflect the change:<\/p>\n\n\n\n

state.observe { currentState ->\n  loadingView.isVisible = currentState is Loading\n}<\/code><\/pre>\n\n\n\n

Event \/ Action<\/h3>\n\n\n\n

An event is a class, which describes a user action or an asynchronous task’s result. Each event is added to an event queue and processed sequentially.<\/p>\n\n\n\n

Here is an example of how event definitions in an application could look:<\/p>\n\n\n\n

interface Event\n\nclass OnResetClicked: Event\ndata class OnApiResult(val result: String): Event\ndata class OnApiError(val error: Throwable): Event<\/code><\/pre>\n\n\n\n

Reduce Function \/ Reducer<\/h3>\n\n\n\n

A reduce function defines exactly two parameters (previous state and current event) and should immediately return a new state.<\/p>\n\n\n\n

\"\"<\/figure><\/div>\n\n\n\n

Here is an example:<\/p>\n\n\n\n

data class State(val isLoading: Boolean, val data: String?)\ndata class OnApiReady(val data: String)\n\nfun reduce(val previousState: State, val event: OnApiReady): State {\n  return previousState.copy(isLoading = false, data = event.data)\n}<\/code><\/pre>\n\n\n\n

Middleware \/ Side Effect<\/h3>\n\n\n\n

Sometimes, there is a need to run an asynchronous task like an API call or writing to a file, etc. Such a task should be processed in a background thread, potentially adding new events to the queue as a result of its execution.<\/p>\n\n\n\n

\"\"<\/figure><\/div>\n\n\n\n

Here is an example:<\/p>\n\n\n\n

data class State(val isLoading: Boolean, val data: String?)\n\ninterface Event\ndata class OnStart(val id: String): Event\ndata class OnCompleted(val data: String): Event\n\nfun middleware(val previousState: State, val event: OnStart) {\n  Thread().run {\n    val objectId = previousState.id\n    \/\/ make an api call for object with objectId identifier\n    \/\/ notify about the result\n    addEvent(OnCompleted(\"result for $objectId\"))\n  }\n}<\/code><\/pre>\n\n\n\n

Types of States<\/h2>\n\n\n\n

There are two types of states: Global<\/em> and Scoped<\/em>.<\/p>\n\n\n\n

\n
\n
\"\"<\/figure><\/div>\n<\/div>\n\n\n\n
\n
\"\"<\/figure><\/div>\n<\/div>\n<\/div>\n\n\n\n

The global state usually reflects the state of the application, while the scoped one reflects the state of a single view fragment such as a Controller<\/em>, ViewModel<\/em>, Presenter,<\/em> or Bloc<\/em>.<\/p>\n\n\n\n

Tips<\/h2>\n\n\n\n