Skip to content

Instantly share code, notes, and snippets.

@zakayothuku
Forked from thealmikey/blog.md
Created April 12, 2021 18:46
Show Gist options
  • Save zakayothuku/c9b1f1ddae8d4edbe027a78e9a0fb0e9 to your computer and use it in GitHub Desktop.
Save zakayothuku/c9b1f1ddae8d4edbe027a78e9a0fb0e9 to your computer and use it in GitHub Desktop.

Using Tinder's Finite State Machine Library to wrap Android's MediaPlayer

When developing software, it's sometimes very easy to get overwhelmed by complexity due to the number of interacting pieces.

This is a problem that's prevalent in all software development from Server-Side development, Front-End development and all the way to Android development.

In a bid to tame complexity software engineers building complex software have looked up to tools that help reduce this burden. One such tool is design using Finite State Machines.

In this tutorial we'll be using Tinder's State Machine to model an Entity and handle complexity of dealing with the many possible states it can be in a manner that makes it almost effortless.

We'll be using Kotlin and Intellij IDE to create our project. We'll select Gradle as our build tool.

The only dependency we'll add for the project will be Tinder's StateMachine library.

We'll add the dependency by adding this to out build.gradle file

https://gist.github.com/2aa07b72c81a9133da000e55acd5c0b6

My complete Gradle file looks as follows, yours could vary depending on the naming and versions of libraries you're using.

The only thing that should be of concern is that you're using Kotlin for the tutorial.

https://gist.github.com/e1984738f4e1be14cacec7195c120e1e

A Finite State Machines works by encoding operational behavior on entities to change their state.

State is a general term to represent properties at a particular moment in time.

A State Machine according to Oxford Dictionary

A device which can be in one of a set number of stable conditions depending on its previous condition and on the present values of its inputs.

A Finite State Machines has a limited number of ways it can be, hence the "finite" part of it.

We use Finite States Machines in software to help us encode properties on the domain elements we're trying to model with our program.

We use knowldge of the domain to encode valid and invalid states and how to transition from one state to another.

If it were the case we were trying to model a human, we can imagine various properties we could model to describe the human's state.

As a small example we could use Emotion property of humans and describe a person's state as maybe either feeling Sad or Happy. Of course those aren't all the human emotions possible but they help demonstrate variance in the Emotion property.

The other aspect of Finite State Machines is Actions. Actions are events that trigger transitioning from one State to another.

With the same happy-human/sad-human example. It's possible to define as many Actions as we desire to make a human transition from either a Happy State to a Sad State and vice versa.

For example we can imagine a "Hug Action" that transitions a human in sad state to a happy state.

We can also imagine a "Get Sick Action" that transitions a happy human to a sad state.

With this in mind. It is also possible to have actions that are run on our Human model but don't transition them to a different state as they're already in that state.

So if for example we provide a "Hug Action" to our human and transition them to a "Happy State" if we do an extra transition that can normally transitions a sad human to a happy human, it would not lead to any transition as the human is already happy. For example if we followed up the "Hug Action" with a "Give Money" action to our happy human, they would still be in the happy state. Of course this isn't completely true or as even the Happy State itself can have variance. This variance is actually encoded in language with something like "Happier" and "Happiest".

This is actually still possible to encode with Hierarchical Finite State Machines where the States that we transition to have some variance that can help describe our model in a more fine-grained fashion. For this example we'll not explore Hierarchical Finite State Machines, but their usage and analogies are all around.

But for the sake of this article we'll stick to a simple example, we'll quote a famous stastician George Box who says:

"all models are wrong, but some are useful".

Going back to happy/sad state example. We can add an extra state of Bored. We can start breaking rules of reality here for demonstration purposes. There's nothing more fun for coders than making Two + Two = 5 :) .

We'll start making rules about human happines and state that humans can't move directly from Happy state to a Sad state, they can only transition from Happy State to Bored State and then they can transition from Bored State to a Sad State.

This way if we try to transition a Human who is Happy to a Sad state this transition will be Invalid. We can express this way : the Action we're trying to use can't work for a Human in that State. But if we were to take a Human who was in a Bored state tried to transition them to a Sad state, the trasintion would be Valid as per our rules. Finite State Machines help us encode the rules on various states and various actions that can make us transition to different state.

So at this moment we have State which describes the properties of a model at a particular time, Happy/Bored/Sad for our example. Actions that are applied to our model e.g Hug-Action/Give-Money-Action/Get-Sick-Action and if valid(as per our rules) transition our model to a different State.

Using Finite State Machines makes modeling this kind of behavior easier and in doing so can help us reduce complexity in our code.

My project structure looks as follows

![Project](Medium/kotlin only/project structure.png)

Tinder's State machine provides us with a nice way to create State machines like the one we have described above.

We'll import the StateMachine object which comes with a create() method which we use to create a state machine.

https://gist.github.com/0fd21be8ad1f552a9de4770e19315ed7

create takes type parameters <State,Action,SideEffect>. States and Actions that can cause transitions are what we use to encode the rules/logic of our domain.

They help us guarantee we're only able to move in the proper places in our code without many if/else branches that can be nested and make it impossible to reason about things.

The SideEffect part represents what we want to do with our code once a transition succeeds..

We'll use the Human example to show a small example.

https://gist.github.com/6998bba9f688df156067046544344348

We'll use sealed classes to model possible Human states and Actions that can be applied to our Human model to make them transition from one state to another ensuring only valid transitions are possible. And finally we encode what we'll do upon valid transition, the side effects we want valid transitions to have on our program SideEffect, for this example I've used UseResult to represent side effect.

After we've described the required components for our domain, we'll plug them into Tinder's state machine that'll help us easily encode Valid transitions for every state.

It helps us encode the default state of the human model when we first interact with them.

https://gist.github.com/8233a3f71b6321b70111b9c9725ad4e7

As our model is trying to encode transitions from emotional state to another. I've named the state to reflect this emotionalState.

After that I plug in state as per our model HumanState in the create() method type parameters followed by action HumanAction and side-effect UseResult.

In the next line we define the initial state our model will have upon creation. In this scenario our Human model (Human class) will by default have the emotion HumanState.Bored. We're free to choose the initial state as per the domain and what we're trying to achieve with our code.

Next we add the States and how different Actions will make them transition to other states.

https://gist.github.com/c2165a3533f162dcbc6073f5c357887d

Once we've described the transition, we add a means to check for Valid transitions and use these valid transitions to call services in our code. This will be in the same block we've described the transitions.

https://gist.github.com/29f950e59967cc1f2c76218b99faabbf

And we're done. We have a state machine that we can use to help us model a Human class and different emotional states the human can be in.

We'll use this finite state machine to model the human class and capture the whole idea.

https://gist.github.com/b6dce376fb194878ccb2c9ab76ce03bb

For the Human class we need a constructor for name and for every instance we create we have a field emotionalState which is a finite state machine that embodies human emotional behavior as per our requirements.

With that we'll create a human instance and transition between different state. We'll create a fun main() to enable us to create an instance of the Human class and run transitions to different states.

https://gist.github.com/51465272c3cfb4a66d492d20154b33e0

Our design of the state machine enables to call HumanAction.GiveTherapy multiple time and we can see that the transitions move us continously to match the logic we put in the state machine.

https://gist.github.com/1a3cbf6bbc9d339d70c667474c7258ba

Our code is easy to reason about and change, giving us the flexibility to use and test our models in our app confidently.

In the next article we'll use Tinder's Finite State Machine to wrap Android's MediaPlayer library in a way that makes it easy to use and reason about.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment