Blog Infos
Author
Published
Topics
, , ,
Published
Who’s Next?!

Who’s Next!?

Finite State Machine (FSM)
sealed class State {
object Idle : State()
object SettingTimer : State()
object CountingDown : State()
object Paused : State()
object Restarting : State()
}
view raw State.kt hosted with ❤ by GitHub

FSM states

 

sealed class Event {
object OnSetTimer : Event()
class OnTimerSet(val value: Int) : Event()
object OnStart : Event()
object OnFinish : Event()
object OnPause : Event()
object OnStop : Event()
object OnReset : Event()
}
view raw Events.kt hosted with ❤ by GitHub

FSM events

 

Who’s Next FSM

Finally, our state machine can also produce SideEffects when transitioning between states:

sealed class SideEffect {
object SetTimer : SideEffect()
class TimerReady(val value: Int) : SideEffect()
object StartCountDown : SideEffect()
object Restarting : SideEffect()
object Pause : SideEffect()
object Stop : SideEffect()
object Reset : SideEffect()
}
view raw SideEffect.kt hosted with ❤ by GitHub

FSM side effects

 

Implementation
val stateMachine = StateMachine.create<State, Event, SideEffect> {
initialState(State)
state<State> {
on<Event> {
transitionTo(State, SideEffect)
}
//more events
}
//more states
onTransition {
//process side effects
}
}
view raw FSM.kt hosted with ❤ by GitHub

DSL

 

Let’s recall our diagram and write the FSM code to alternate between both Idle and SettingTimer states:

FROM -> TO by [EVENT]
Idle -> SettingTimer by [OnSetTimer]
SettingTimer -> Idle by [OnTimerSet]
StateMachine.create<TimerState.State, TimerState.Event, TimerState.SideEffect> {
initialState(TimerState.State.Idle)
state<TimerState.State.Idle> {
on<TimerState.Event.OnSetTimer> {
transitionTo(TimerState.State.SettingTimer, TimerState.SideEffect.SetTimer)
}
}
state<TimerState.State.SettingTimer> {
on<TimerState.Event.OnTimerSet> {
transitionTo(TimerState.State.Idle, TimerState.SideEffect.TimerReady(it.value))
}
}
onTransition {
val validTransition = it as? StateMachine.Transition.Valid ?: return@onTransition
//emit ui state changes
}
}
view raw FSM.kt hosted with ❤ by GitHub

Idle and SettingTimer configuration

 

And the result is:

FSM operations output

 

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

,

Blast Off_ Managing Hundreds of UI Updates for Emoji Cannons

Managing a state might be a challenge. Managing the state with hundreds of updates and constant recomposition of floating emojis is a challenge indeed.
Watch Video

Blast Off_ Managing Hundreds of UI Updates for Emoji Cannons

Piotr Prus
Android developer

Blast Off_ Managing Hundreds of UI Updates for Emoji Cannons

Piotr Prus
Android developer

Blast Off_ Managing Hundreds of UI Updates for Emoji Cannons

Piotr Prus
Android developer

Jobs

Model-View-Intent (MVI)
Implementation
data class TimerUiState(
val value: Int = 0, //in seconds
val progress: Float = 0f, //0-100
val isSettingTimer: Boolean = false,
val isCountingDown: Boolean = false,
val isRestarting: Boolean = false
)
view raw TimerUiState.kt hosted with ❤ by GitHub

MVI State

 

class TimerViewModel : ViewModel() {
private val _state = MutableLiveData<TimerUiState>(TimerUiState())
val state: LiveData<TimerUiState> = _state
fun settingTime() {
_state.value = state.value.copy(isSettingTimer = true, isCountingDown = false, isRestarting = false)
}
fun setTime(seconds: Int) {
_state.value = TimerUiState(seconds)
}
}
//Observing state changes in the View
@Composable
fun TimerScreen(viewModel: TimerViewModel) {
with(viewModel.state.observeAsState().value) { /*...*/ }
}

MVVM implementation

 

With Orbit Multiplatform we change it to:

class TimerViewModel : ViewModel(), ContainerHost<TimerUiState, Nothing> {
override val container = viewModelScope.container<TimerUiState, Nothing>(TimerUiState())
fun settingTime() {
intent { reduce { state.copy(isSettingTimer = true, isCountingDown = false, isRestarting = false) } }
}
fun setTime(seconds: Int) {
intent { reduce { TimerUiState(seconds) } }
}
}
//Collecting state changes in the View
@Composable
fun TimerScreen(viewModel: TimerViewModel) {
with(viewModel.collectAsState().value) { /*...*/ }
}

MVI implementation

 

class TimerViewModel : ViewModel(), ContainerHost<TimerUiState, Nothing> {
override val container = viewModelScope.container<TimerUiState, Nothing>(TimerUiState())
private val stateMachine = StateMachine.create<TimerState.State, TimerState.Event, TimerState.SideEffect> {
initialState(TimerState.State.Idle)
state<TimerState.State.Idle> {
on<TimerState.Event.OnSetTimer> {
transitionTo(TimerState.State.SettingTimer, TimerState.SideEffect.SetTimer)
}
}
state<TimerState.State.SettingTimer> {
on<TimerState.Event.OnTimerSet> {
transitionTo(TimerState.State.Idle, TimerState.SideEffect.TimerReady(it.value))
}
}
onTransition {
val validTransition = it as? StateMachine.Transition.Valid ?: return@onTransition
when (val effect = validTransition.sideEffect as TimerState.SideEffect) {
is TimerState.SideEffect.SetTimer -> intent { reduce { state.copy(isSettingTimer = true, isCountingDown = false, isRestarting = false) } }
is TimerState.SideEffect.TimerReady -> intent { reduce { TimerUiState(effect.value) } }
}
}
}
fun settingTime() {
stateMachine.transition(TimerState.Event.OnSetTimer)
}
fun setTime(seconds: Int) {
stateMachine.transition(TimerState.Event.OnTimerSet(seconds))
}
}
//Collecting state changes in the View
@Composable
fun TimerScreen(viewModel: TimerViewModel) {
with(viewModel.collectAsState().value) { /*...*/ }
}

FSM (state: Idle and SettingTimer) + MVI implementation

 

FSM+MVI

Conclusions

Who’s Next!?

Final thoughts

Thanks to Matthew Dolan.

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
In this part of the series, we will plan our first screen in Jetpack…
READ MORE
blog
We’ll be selecting a time whenever a user presses a number key. Following points…
READ MORE
blog
In the first two articles, we explored the challenges of implementing traditional Clean Architecture…
READ MORE
blog
Ask yourself a fairly standard question for any interview to fill an Android position:…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu