State Machine, From a Diagram to Code
By: Terrick Mansur
Tags: Functional Programming, Software Development, iOS, Development, iOS App Development

In this article, I will be showing you four simple steps you can use, to convert a state machine diagram into working code. These steps will make writing state machines so easy, that you will want to have all your stories described in state machine diagrams.

Photo by Yogesh Pedamkar on Unsplash

Before we get into the solution, let's understand what a state machine is. A state machine is a system that takes in events and produces new states given the accumulated events that it has received.

This diagram is a very common state machine that we find in most applications.

It describes fours states the system can be in; “Not started”, “Loading”, “Error”, “Succes”, and fours event that the system can receive “Fetch”, “Refresh”, “Error”, “Success”.

Let’s start going through the four steps to convert this into working code.

Step 1:

Create your event enum.

For the first step, all we need to do is create an enum and list all the events as cases . If you look at the diagram, the events are the arrows. As you can see, each arrow has a name. These names will be cases in our enum. The example diagram has 4 different event kinds arrow, “Fetch”, “Refresh”, “Error”, “Success”. Let’s put then in an enum .

Step 2:

Create your state enum.

The state enum is as easy to create as the event enum . All you need to do it list the states and create an enum with a case for each of the listed state . States are represented as the bubbles in a diagram, we have four states, "Not Started", "Loading", "Error", "Success". Let's create our enum .

Step 3:

Write your reducer.

A "reducer" is just a fancy word for a function that takes in a state (current state), an event and returns a state (new state).

With this information, we can already declare the function signature.

func reducer(currentState: State, event: Event) -> State { ... }

Before we write the implementation, let's look at the diagram again. Specifically how the arrows are drawn, where they originate from, and where they point to. This will guide us when we are implementing the reducer. Let's look at the "Success" arrow.

The "Success" arrow originates from the state "Loading" and points to the state "Success". In code terms, we can say, if the current state is "Loading" and we receive a "Success" event, then update the current state to "Success". Let's write this in code.

if currentState == .loading && event == .success {

newState = .success

}

Let's look at our next arrow, "Fetch". "Fetch" originates from state "Not Started" and point to state "Loading". Let's write the if statement for the “Fetch” arrow.

if currentState == .notStarted && event == .fetch {

newState = .loading

}

If we do this for each of the arrows we would end up with the five if statements below (the numbers of arrows there are in the diagram).

if currentState == .loading && event == .success {

newState = .success

}

if currentState == .notStarted && event == .fetch {

newState = .loading

}

if currentState == .loading && event == .error {

newState = .error

}

if currentState == .error && event == .refresh {

newState = .loading

}

if currentState == .success && event == .refresh {

newState = .loading

}

I think it’s safe to say that anyone with little coding experience can see here that the last fours if statements can be made into else if .

However, for languages that support switches with multiple parameters, using a switch would be an even better solution. Since Swift supports this, and Swift is the language I know the best, I will be converting these if statements into a switch and adding it to the reducer function which we defined the signature for earlier.

You might have noticed, that we had added default in our switch. The switch will only hit the default when there is a case that is not defined by the diagram. Some of these cases might not even exist, an example would if an error event occur when we are in state notStarted . This can never happen, because how can we get an error if nothing has happened yet? In these cases we just return the current state passed in, meaning no change to the state .

Step 4:

Put it all together

At this point, we have everything we need to start using our code. Your reducer works and will give you the correct results. The logic is written and the code is valid. But let's put it all into a nice class/struct.

In our struct, we have our two enums Event and State as sub-types. We have our reducer function as a static private method (the reducer can also just be passed in), we have an update function that takes in an Event , and we have a currentState variable with a private set , so only the struct can change it.

Once you instantiate this state machine, there is only one way to interface with it, and that is by calling the update functions. Other then that, you can only read the currentState . Let’s write the unit test to see how it works.

It works! That's it, your state machine is done.

Pro tip:

Passing values with your events.

In most cases both your events and states will have values. For example, the error event will most likely have an “error message” and your error state most likely also have a userMessage (the message you will show to your user).

To add these values to our states and events , all we need to do is add associated values for our cases. Here is how that would look if we were to add all the values for our Event and State enums.

Although the values are not indicated in our example diagram, you can include them by writing “Error: String” as the label for the arrow, and “Error: String” as the label for the state .

Closing remarks:

Anyone can draw a state machine.

Developers sometimes shy away from writing state machines. The term sounds like a fancy/complicated system. But in reality, it’s very simple. The biggest benefit to following these steps in my opinion is that anyone can write a state diagram, a designer, a product owner, or a client, and with these steps you are going to be able to code exactly what they want. State machine diagrams are a very precise way to describe how you want your system to behave. If you can get your requirements in the form of a state diagram, it will become very easy to write correct, testable, and scalable code.

Thank you for reading

If you liked this article please follow me and show your support with a clap.

Get in touch: