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


    Senior Android Engineer

    Carly Solutions GmbH
    Munich
    • Full Time
    apply now

    Senior Android Developer

    SumUp
    Berlin
    • Full Time
    apply now

OUR VIDEO RECOMMENDATION

, ,

Boosting Compose UI from Sluggish to Snappy

Join us on an enlightening quest as we unravel the realm of Compose UI performance. With a multitude of tools at our disposal, the challenge lies in knowing where to start and how to choose.
Watch Video

Boosting Compose UI from Sluggish to Snappy

Akshay Chordiya & Tasha Ramesh
Android Developer & Android
Tinder

Boosting Compose UI from Sluggish to Snappy

Akshay Chordiya & ...
Android Developer & ...
Tinder

Boosting Compose UI from Sluggish to Snappy

Akshay Chordiya ...
Android Developer & Andro ...
Tinder

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
Today I aim to cover the Domain layer. It is a layer that sits…
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