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

, ,

Building State Holders in Compose with Molecule: A New Approach to Reusable UI Components

Are your ViewModels exponentially growing out of control as they manage the state for each of your Composables? This talk introduces Molecule, a new library for creating state holders in Jetpack Compose.
Watch Video

Building State Holders in Compose with Molecule: A New Approach to Reusable UI Components

Jack Adams
Senion Android Engineer
Trainline

Building State Holders in Compose with Molecule: A New Approach to Reusable UI Components

Jack Adams
Senion Android Engin ...
Trainline

Building State Holders in Compose with Molecule: A New Approach to Reusable UI Components

Jack Adams
Senion Android Engineer
Trainline

Jobs

No results found.

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
Menu