Implementing UIs with state machines

In this section, we make the link between reactive systems, behavior, state machines, and Kingly. We however strive to remain concise. You can alternatively review previously published long-form articles that gently go more in depth:

Implementing an application with state machines consists of two steps:

The application behavior is simply the relation between events received by the interface and commands to perform on the interfaced systems. Implementing the actual application includes performing the commands on the interfaced systems and creating listeners for the events accepted by the UI.

Architecture

Kingly is thus designed to encourage a UI implementation consisting of separate modules with delimited responsibilities:

kingly-favoured ui architecture

The interfaced systems — that includes the input device (e.g. your laptop or mobile phone) — are operated by command handlers. Interfaced systems can:

Command handlers may react to responses from interfaced systems (e.g., remotely fetched data) by passing on events to the machine. The machine computes commands from the events it receives which are executed by command handlers. Those commands may result in API calls on the interfaced systems, which in turn may result in responses.

Note that the output device (e.g., a screen), is also one of the interfaced systems, and rendering is simply a command to an interfaced output device.

Modeling behavior

The behavior of an application can be specified by a relation between events received by the application and actions to be performed as a result on the interfaced system. With Kingly, you are expressing this relation with a procedure fsm which takes an event received by the interface into commands to perform on the interfaced systems: commands = fsm(event).

In most of the cases, the same event may result in different commands to be executed. This means that the fsm procedure is a stateful one. A Kingly state machine is such a function, which encapsulates the necessary state so that it is only accessible and visible to the fsm function itself. If we make the encapsulated state visible, we can define a pure function $g$ such that $(actions_n, state_{n+1}) = g (state_n, event_n)$, where $n$ is the $n^{th}$ invocation of the fsm function for the $n^{th}$ input it processes.

Additionally, a Kingly state machine divides its encapsulated state in two kinds of state: control states, and extended state. The machine starts in an initial control state. For each control state, the state machine must define a sub-procedure fsm' which will:

Control states thus define a “formula” computing the commands, while the extended state gathers the data necessary to perform the computation. As the machine changes of control state, the computation to perform changes accordingly. Control states may for instance serve to model interfaces going through different screens, associating a different relation between events and commands (i.e. behavior) to each screen.

State machines can be visually represented by a graph that links two control states whenever there is a possible transition between the two control states. The visualized graph summarizes accurately the fsm computation, i.e. the behavior of the interface to implement.

Modeling an interface behavior with state machines has the following advantages:

Using Kingly as a state machine library has the following advantages:

Application implementation

You should always start with getting a refined understanding of the application behavior from its informal specifications. From there, the previously described architecture allows you to decide in which order to go about implementing the separate modules. After some time doing this, we found the following workflow convenient:

Pick the order that makes more sense for you. Understanding what a screen depends on (props), and what are the events that it may generate also often helps define the set of events accepted by the machine; the parameters that the machine must compute for rendering a screen; and the set of commands executed by command handlers.

We will address automated test generation in a future dedicated section.

This section was fairly theoretical. We encourage you to review the tutorials to see these concepts and architecture put in practice. You can then come back and forth between tutorials and concepts to further your understanding. Let us know if you are stuck!

Alternatively, you can refer to the site map and navigate to other parts of the documentation.