Now a days State Machine is becoming quite popular amongst the Android developer community.
Developing a state machine can be tedious job, and there is no point in spending so much time and energy to build an error free state-machine instead of working on our business logic or functionalities.
So, when I was in search of a good, out-of-the box state machine, which I can integrate directly into the app. I came across this one,
Guess what, this is built on Kotlin 👊
Now, coming to use cases, Where can I use this state machine? What scenarios can I fit this state machine in?
Automotive
We get to see Android Auto for infotainment in almost all the latest cars. When we build apps for android auto, we need to maintain the state, like, a car can be in Ignited State, Moving State, Parking State, Reverse Gear State or Switched Off. Based on each state of the car the functions of the car will change. Simple example, when the car is in driving state, we have to disable most of the operations in infotainment, like, user cannot connect his phone via Bluetooth. Similarly, when car is in reverse gear, front and rear camera should be turned ON and start streaming to the infotainment. In all these use cases, we need to maintain the state of the car to take decisions on what actions to perform and what are restricted. So, we need a state machine here to maintain different states and enabled/disable features , hide/show actions.
Smart Buildings or Smart Devices
When we are communicating with various smart devices, each device will have its features. Each feature is a state, for example thermostat, when it is cool out side, it should warm the room inside as per the configuration, when it is hot outside, it should cool the room inside. So, thermostat can be in ON state, Off State, Cooling Mode, Heating Mode and etc. When we connect to this device via some communication or protocol we/app should know the state of the device and also know the transition from one state to another, based on these state certain actions has to be enabled or disabled in the app.
The need of this state machine can arise is many more filed like, Medical devices, Industrial, Aero and etc.
Additionally, we can use this state machine in pure IT products, where we have asynchronous calls, which involves, REST based Request and Response and receive events from a web socket, i.e., Send Request, get Response immediately, and wait for WebSocket events asynchronously.
No, we will see a basic example about how to use and integrate this KStateMachine into our app.
The below simple app has 3 states, Green, Yellow, Red. Let Acceptance Criteria be.
- Our state machine can be in Green State and move to Yellow State.
- From yellow state, it can move to either Red State or Green State.
- State machine cant move from Green to Red state directly.
- If state machine moves to Red state, It cant move to any other state, it is a final state.
- And, finally we can rest the state.
Lets Start to build the app, Again, this is just a simple/sample app we should not directly use this in our app AS IS but follow proper design patters and SOLID principles.
Step 1: Add the KStateMachine Dependency to app gradle.
// State Machine
implementation 'com.github.nsk90:kstatemachine:0.6.0'
Step 2: Create sealed class for States,
import ru.nsk.kstatemachine.DefaultFinalState
import ru.nsk.kstatemachine.DefaultState
sealed class States {
object GreenState: DefaultState("Green")
object YellowState: DefaultState("Yellow")
object RedState: DefaultFinalState("Red")
}
Step 3: Create sealed class for Events,
import ru.nsk.kstatemachine.Event
sealed class Events {
object YellowEvent: Event
object RedEvent: Event
object GreenEvent: Event
}
Step 4: Create a simple layout to represent all these states,
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".KStateMachineActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<Button
android:id="@+id/greenBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:onClick="green"
android:text="Green" />
<Button
android:id="@+id/yellowBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:onClick="yellow"
android:text="Yellow" />
<Button
android:id="@+id/redBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:onClick="red"
android:text="Red" />
</LinearLayout>
<View
android:id="@+id/stateRep"
android:layout_width="290dp"
android:layout_height="290dp"
android:layout_gravity="center"
android:background="@color/green"/>
<Button
android:layout_width="290dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="5dp"
android:onClick="reset"
android:text="Reset" />
</LinearLayout>
Step 5: Our main activity will look like below, If we go through this code for couple of times and play around with the app, we will understand how the state machine works.
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.karthik.scribble.statemachine.Events
import com.karthik.scribble.statemachine.States
import ru.nsk.kstatemachine.*
class KStateMachineActivity : AppCompatActivity(R.layout.activity_kstate_machine) {
lateinit var machine: StateMachine
lateinit var stateRep: View
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
stateRep = findViewById(R.id.stateRep)
initializeStateMachine()
}
private fun initializeStateMachine() {
machine = createStateMachine(name = "Scribble Machine", start = true) {
/*Initial State - Green State - Start */
addInitialState(States.GreenState) {
onEntry {
Log.d("Karthik", "Enter $name state")
stateRep.setBackgroundColor(getColor(R.color.green))
}
transition<Events.YellowEvent> {
targetState = States.YellowState
onTriggered {
Log.d("Karthik", "Transition on YellowEvent")
}
}
transition<Events.GreenEvent> {
onTriggered {
Log.d("Karthik", "Transition on GreenEvent")
}
}
transition<Events.RedEvent> {
onTriggered {
Log.d("Karthik", "Transition on RedEvent")
}
}
onExit {
Log.d("Karthik", "Exit $name state")
}
}
/*Initial State - Green State - End */
/*State - Yellow State - Start */
addState(States.YellowState) {
onEntry {
Log.d("Karthik", "Enter $name state")
stateRep.setBackgroundColor(getColor(R.color.yellow))
}
transition<Events.GreenEvent> {
targetState = States.GreenState
onTriggered {
Log.d("Karthik", "Transition on GreenEvent")
}
}
transition<Events.RedEvent> {
targetState = States.RedState
onTriggered {
Log.d("Karthik", "Transition on RedEvent")
}
}
onExit {
Log.d("Karthik", "Exit $name state")
}
}
/*State - Yellow State - End */
/*Final State - Red State - Start */
addFinalState(States.RedState) {
onEntry {
Log.d("Karthik", "Enter $name state")
stateRep.setBackgroundColor(getColor(R.color.red))
}
onExit {
Log.d("Karthik", "Exit $name state")
}
}
/*State - Red State - End */
}
}
fun red(v: View) {
machine.processEvent(Events.RedEvent)
}
fun yellow(v: View) {
machine.processEvent(Events.YellowEvent)
}
fun green(v: View) {
machine.processEvent(Events.GreenEvent)
}
fun reset(v: View) {
machine.stop()
machine.start()
}
}
Take One Step at a time, Have fun with State-Machine Kotlin/Android. ~Karthik Harpanhalli