### Starting State Machine on Enter Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/13_composing-statemachines.md This example illustrates how to use `onEnterStartStateMachine` to initiate a state machine when a state is entered. It requires a `stateMachineFactoryBuilder` and a `stateMapper` to handle state transitions. ```kotlin spec { inState { onEnterStartStateMachine( stateMachineFactoryBuilder = { SomeFlowReduxStateMachine() }, stateMapper = { someOtherStateMachineState : S -> override { ... } } ) } } ``` -------------------------------- ### Starting State Machine on Action with Detailed Configuration Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/13_composing-statemachines.md This snippet shows the initial, more verbose way of using `onActionStartStateMachine`. It requires explicit lambda definitions for `stateMachineFactory` and `stateMapper`, making it less readable than the custom DSL approach. ```kotlin spec { inState { // Quite a bit of unreadable code onActionStartStateMachine( stateMachineFactory = { action: ToggleFavoriteItemAction, stateSnapshot : ShowContent -> val item : Item = stateSnapshot.items.find { it == action.itemId} // create and return a new FavoriteStatusStateMachine instance FavoriteStatusStateMachine(item, httpClient) }, stateMapper = { itemListState : State, favoriteStatus :FavoriteStatus -> itemListState.mutate { val itemToReplace : Item = this.items.find { it == favoriteStatus.itemId } val updatedItem : Item = itemToReplace.copy(favoriteStatus = favoriteStatus) // Create a copy of ShowContent state with the updated item this.copy(items = this.items.copyAndReplace(itemToReplace, updatedItem) ) } } ) } } ``` -------------------------------- ### Readable State Machine Composition with Custom DSL Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/13_composing-statemachines.md This example demonstrates a more readable way to define state machine transitions within a `spec` block using custom Kotlin extension functions and receivers. It simplifies the `onActionStartStateMachine` usage by abstracting factory and state mapping logic. ```kotlin spec { inState { onEnter { loadItemsAndMoveToContentOrError(it) } } inState { on { action, state -> state.override { Loading } } collectWhileInState(timerThatEmitsEverySecond()) { value, state -> decrementCountdownAndMoveToLoading(value, state) } } inState { onActionStartStateMachine({ action -> itemStateMachine(action.itemId) }) { favoriteStatus -> updateItemWithFavoriteStatus(favoriteStatus) } } } private fun State.itemStateMachine(itemId : Int) : FavoriteStatusStateMachine { val item : Item = snapshot.items.find { it == itemId } // create and return a new FavoriteStatusStateMachine instance FavoriteStatusStateMachine(item, httpClient) } private fun ChangeableState.updateItemWithFavoriteStatus(favoriteStatus : FavoriteStatus) : ChangedState { return mutate { val itemToReplace : Item = this.items.find { it == favoriteStatus.itemId } val updatedItem : Item = itemToReplace.copy(favoriteStatus = favoriteStatus) // Create a copy of ShowContent state with the updated item this.copy(items = this.items.copyAndReplace(itemToReplace, updatedItem)) } } ``` -------------------------------- ### onActionStartStateMachine() Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/13_composing-statemachines.md Starts a new state machine when a specific action is triggered within a parent state machine. The child state machine's lifecycle is tied to the parent's state. It allows for complex state management by breaking down functionality into smaller, manageable state machines. ```APIDOC ## `onActionStartStateMachine()` ### Description Starts a new state machine in response to a specific action within the current state machine. The lifecycle of the started state machine is managed by the parent state machine, typically ending when the parent transitions out of the state where it was initiated. ### Parameters 1. `stateMachineFactoryBuilder: State.(Action) -> FlowReduxStateMachineFactory` * Description: A lambda function used to create and configure the new state machine. It receives the current state of the parent state machine and the triggering action, allowing for the creation of a specific child state machine instance based on these inputs. 2. `handler: ChangeableState.(StateOfNewStateMachine) -> ChangedState` * Description: A lambda function responsible for merging the state of the newly started child state machine into the parent state machine's state. It provides access to the parent's `ChangeableState` and the child's current state, enabling state updates via methods like `override` or `mutate`. 3. `actionMapper: (Action) -> OtherStateMachineAction?` (Optional) * Description: A lambda function to map actions from the parent state machine to actions that the child state machine can handle. If an action is not meant to be forwarded, it can return `null`. This is useful for inter-state machine communication. ### Lifecycle The state machine started with `onActionStartStateMachine()` remains active as long as the surrounding `inState` block in the parent state machine is active. When the parent state machine transitions out of this state, the child state machine is automatically canceled. ``` -------------------------------- ### Test Initial State with Turbine Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/14_testing.md Verify that the state machine starts with its initial state. Turbine's `test` function and `awaitItem` are used to assert the first emitted state. ```kotlin import kotlinx.coroutines.test.runTest @Test fun `state machine starts with Loading state`() = runTest { val stateMachine = ItemListStateMachineFactory(HttpClient()).shareIn(backgroundScope) stateMachine.state.test { // awaitItem() from Turbine waits until next state is emitted. // FlowReduxStateMachine emits initial state immediately. assertEquals(Loading, awaitItem()) } } ``` -------------------------------- ### Default Action Execution in FooState Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/11_ExecutionPolicy.md This example demonstrates a basic action handler within a specific state. By default, it uses CancelPrevious policy, meaning any prior execution of BarAction in FooState will be canceled if a new one is triggered before the delay completes. ```kotlin spec { inState { on { delay(5000) // wait for 5 seconds override { OtherState() } } } } ``` -------------------------------- ### Start Child State Machine on Action Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/13_composing-statemachines.md Use `onActionStartStateMachine` to launch a new state machine when a specific action is dispatched. The child state machine runs as long as the parent state is active. Configure how the child's state updates the parent's state using the handler. ```kotlin inState { // on ToggleFavoriteItemAction start state machine onActionStartStateMachine( stateMachineFactoryBuilder = { action: ToggleFavoriteItemAction -> val item : Item = snapshot.items.find { it == action.itemId } // create and return a new FavoriteStatusStateMachine instance FavoriteStatusStateMachine(item, httpClient) } ) { favoriteStatus: FavoriteStatus -> mutate { val itemToReplace: Item = this.items.find { it == favoriteStatus.itemId } val updatedItem: Item = itemToReplace.copy(favoriteStatus = favoriteStatus) // Create a copy of ShowContent state with the updated item this.copy(items = this.items.copyAndReplace(itemToReplace, updatedItem)) } } } ``` -------------------------------- ### Apply Actions to Subset of States Using Intermediate Interface Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/10_accross-multiple-states.md Use `inState` in your DSL specs to apply actions to a specific subset of states. For example, actions within `inState` will be active for both `ShowContent` and `Error` states. ```kotlin // DSL specs spec { inState { // on, onEnter, collectWhileInState for all PostLoading states. // It means as long as we are in ShowContent or Error state this DSL block // is active. } inState { // on, onEnter, collectWhileInState for all ListState states, so for all states of this state machine. } inState { // on, onEnter, collectWhileInState specific to Loading state only } inState { // on, onEnter, collectWhileInState specific to ShowContent state only } ... } ``` -------------------------------- ### Unit Test FlowRedux Handler Logic Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/14_testing.md This example shows how to unit test a FlowRedux handler function. It requires extracting the handler logic into a suspend function and then testing it with a mocked HTTP client and FlowRedux's `ChangeableState`. ```kotlin spec { inState { onEnter { loadItemsAndMoveToContentOrError(it) } } } suspend fun loadItemsAndMoveToContentOrError(state: State): ChangedState { return try { val items = httpClient.loadItems() state.override { ShowContent(items) } } catch (t: Throwable) { state.override { Error(message = "A network error occurred", countdown = 3) } } } ``` ```kotlin @Test fun `on HTTP success move to ShowContent state`() = runTest{ val items : List = generateSomeFakeItems() val httpClient = FakeHttpClient(successResponse = items) val stateMachine = ItemListStateMachineFactory(httpClient) val startState = ChangeableState(Loading) // Create a FlowRedux ChangeableState object val changedState : ChangedState = stateMachine.loadItemsAndMoveToContentOrError(startState) val result : ListState = changedState.reduce(startState.snapshot) // FlowRedux API: you must call reduce val expected = ShowContent(items) assertEquals(expected, result) } ``` -------------------------------- ### Use untilIdentityChanged in FlowRedux spec Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/9_untilIdentityChanged.md This example demonstrates using untilIdentityChanged within a FlowRedux state machine specification. The block inside untilIdentityChanged will restart whenever the selected email's ID changes. It loads email details when an email is selected and the state machine is in InboxState. ```kotlin spec { inState { onEnter { loadEmails() } on { action, state -> state.mutate { copy(selectedEmail = SelectedEmail( emailId = action.selectedEmail.id, details = null )) } } untilIdentityChanged({ state -> state.selectedEmail?.emailId }) { // This block will be canceled and restarted whenever emailId changes onEnter { state -> val s = state.snapshot if (s.selectedEmail != null) { val details = loadEmailDetails(s.selectedEmail.emailId) state.mutate { copy(selectedEmail = selectedEmail.copy(details = details)) } } else { state.noChange() } } } } } ``` -------------------------------- ### Test Loading to ShowContent Transition Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/14_testing.md Assert the transition from the `Loading` state to the `ShowContent` state upon a successful HTTP response. This test uses a `FakeHttpClient` to simulate the response. ```kotlin import kotlinx.coroutines.test.runTest @Test fun `move from Loading to ShowContent state on successful HTTP response`() = runTest { val items : List = generateSomeFakeItems() val httpClient = FakeHttpClient(successResponse = items) val stateMachine = ItemListStateMachineFactory(httpClient).shareIn(backgroundScope) stateMachine.state.test { assertEquals(Loading, awaitItem()) // initial state assertEquals(ShowContent(items), awaitItem()) // loading successful --> ShowContent state } } ``` -------------------------------- ### Test Loading to Error Transition Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/14_testing.md Verify the transition from the `Loading` state to the `Error` state when an HTTP request fails. A `FakeHttpClient` is used to simulate an error response. ```kotlin import kotlinx.coroutines.test.runTest @Test fun `move from Loading to Error state on error HTTP response`() = runTest { val exception = IOException("fake exception") val httpClient = FakeHttpClient(error = exception) val stateMachine = ItemListStateMachineFactory(httpClient).shareIn(backgroundScope) stateMachine.state.test { assertEquals(Loading, awaitItem()) // initial state assertEquals(Error(message = "A network error occurred", countdown = 3), awaitItem()) } } ``` -------------------------------- ### Tagging a New Release Source: https://github.com/freeletics/flowredux/blob/main/RELEASING.md Use this command to create a new annotated tag for a release. Replace X.Y.Z with the actual version number. ```bash git tag -a X.Y.Z -m "Version X.Y.Z" ``` -------------------------------- ### State Machine Initialization and Specification Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/12_improve-readability.md Defines a state machine with initial state and transitions for Loading, Error, and Content states. Includes network loading logic and error handling with a countdown timer. ```kotlin class ItemListStateMachineFactory( private val httpClient: HttpClient ) : FlowReduxStateMachineFactory() { init { initializeWith { Loading } spec { inState { onEnter { // We have discussed this block already in a previous section try { val items = httpClient.loadItems() override { ShowContent(items) } } catch (t: Throwable) { override { Error("A network error occurred", countdown = 3) } // countdown is new } } } inState { on { action: RetryLoadingAction -> // We have discussed this block already in a previous section state.override { Loading } } val timer : Flow = timerThatEmitsEverySecond() collectWhileInState(timer) { timerValue: Int -> // This block triggers every time the timer emits // which happens every second state.override { // we use .override() because we could move to another type of state // inside this block, this references Error state if (this.countdown > 0) { this.copy(countdown = this.countdown - 1) // decrease countdown by 1 second } else { Loading // transition to the Loading state } } } } } } private fun timerThatEmitsEverySecond(): Flow = flow { var timeElapsed = 0 while (isActive) { // Is Flow still active? delay(1000) // Wait 1 second timeElapsed++ emit(timeElapsed) // Flow emits value } } } ``` -------------------------------- ### Run Ktlint Formatter Source: https://github.com/freeletics/flowredux/blob/main/docs/contributing.md Execute this command to format the codebase according to Ktlint standards. The CI also runs this check. ```shell # This runs the formatter ./kotlinw scripts/ktlint.main.kts ``` -------------------------------- ### Initialize FlowRedux State Machine Factory Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/1_basics.md Sets up the initial state of the state machine to `Loading` and prepares the specification block for defining state transitions. ```kotlin class ItemListStateMachineFactory( private val httpClient: HttpClient ) : FlowReduxStateMachineFactory() { init { initializeWith { Loading } spec { // will be filled in next section ... } } } ``` -------------------------------- ### Configure Snapshot Repository Source: https://github.com/freeletics/flowredux/blob/main/README.md Add the Sonatype snapshot repository to your `allprojects` block to use the latest snapshot versions directly from the `main` branch. ```groovy allprojects { repositories { // Your repositories. // ... // Add url to snapshot repository maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } } } ``` -------------------------------- ### Using override() to Transition to a New State Type Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/4_State-ChangedState.md Demonstrates the correct usage of override() to transition from a Loading state to an Error state. This clearly indicates a change in the state's type. ```kotlin spec { inState { onEnter { override { Error() } // OK: move from Loading to Error state } } inState { onEnter { state.mutate { Error() } // compiler error! } } } ``` -------------------------------- ### Pushing Tags to Remote Source: https://github.com/freeletics/flowredux/blob/main/RELEASING.md After creating a local tag, push all tags to the remote repository. ```bash git push --tags ``` -------------------------------- ### Use Snapshot Version Source: https://github.com/freeletics/flowredux/blob/main/README.md Append `-SNAPSHOT` to the version number to include the latest snapshot build in your project dependencies. ```groovy implementation 'com.freeletics.flowredux:flowredux:1.2.1-SNAPSHOT' ``` -------------------------------- ### Define State Behavior with DSL Blocks Source: https://github.com/freeletics/flowredux/blob/main/docs/dsl-cheatsheet.md Use 'inState' to define behavior for a specific state. Handle actions, side effects, and state transitions within this block. ```kotlin spec { inState{ on { action -> ... } on(ExecutionPolicy) { action -> ... } onEnter { ... } onEnter { ... } collectWhileInState(flow1) { valueEmittedFromFlow -> ... } collectWhileInState(flow2) { valueEmittedFromFlow -> ... } onActionEffect { action -> ... } onEnterEffect { ... } collectWhileInStateEffect(flow1) { valueEmittedFromFlow -> ... } onEnterStartStateMachine( stateMachineFactoryBuilder = { stateSnapshot : State1 -> OtherStateMachine() }, ) { otherStateMachineState : OtherState -> ... } onEnterStartStateMachine(...) onActionStartStateMachine(...) untilIdentityChanged({ state.id }) { on { action -> ... } onEnter { ... } collectWhileInState(flow) { valueEmittedFromFlow -> ... } onActionEffect { action -> ...} onEnterEffect { ... } collectWhileInStateEffect(flow) { valueEmittedFromFlow -> ... } onEnterStartStateMachine(...) onActionStartStateMachine(...) } condition({ state.someString == "Hello" }){ on { action -> ... } onEnter { ... } collectWhileInState(flow) { valueEmittedFromFlow -> ... } onActionEffect { action -> ...} onEnterEffect { ... } collectWhileInStateEffect(flow) { valueEmittedFromFlow -> ... } onEnterStartStateMachine(...) onActionStartStateMachine(...) untilIdentityChanged(...) { on { action -> ... } onEnter { ... } collectWhileInState(flow) { valueEmittedFromFlow -> ... } onActionEffect { action -> ...} onEnterEffect { ... } collectWhileInStateEffect(flow) { valueEmittedFromFlow -> ... } onEnterStartStateMachine(...) onActionStartStateMachine(...) } } } inState { ... } } ``` -------------------------------- ### Apply Actions to All States Using Base Class Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/10_accross-multiple-states.md Use `inState` in your DSL specs to apply actions, enter handlers, or collection logic to all states that inherit from the specified base class. These blocks are never canceled as long as the base class is in scope. ```kotlin spec { inState { // on, onEnter, collectWhileInState for all states because // ListState is the base class, thus these never get canceled } inState { // on, onEnter, collectWhileInState specific to Loading state only } inState { // on, onEnter, collectWhileInState specific to ShowContent state only } // ... } ``` -------------------------------- ### ChangeableState API Overview Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/4_State-ChangedState.md This simplified API shows the core functions of ChangeableState: snapshot for reading the current state, override() for replacing the state, mutate() for updating the state, and noChange() for explicitly not changing the state. ```kotlin class ChangeableState { val snapshot : T fun override(newState : () -> T) : ChangedState fun mutate(block: T.() -> T ) : ChangedState fun noChange() : ChangedState } ``` -------------------------------- ### Test Error Countdown to Loading Transition Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/14_testing.md Verify that the state machine transitions to the `Loading` state once the error countdown reaches zero. This test uses `initializeWith` to set an initial `Error` state with a specific countdown. ```kotlin import kotlinx.coroutines.test.runTest @Test fun `once Error countdown is 0 move to Loading state`() = runTest { val msg = "A network error occurred" val initialState = Error(message = msg, countdown = 3) val factory = ItemListStateMachineFactory(httpClient) factory.initializeWith { initialState } val stateMachine = factory.shareIn(backgroundScope) stateMachine.state.test { assertEquals(initialState, awaitItem()) assertEquals(Error(msg, 2)) assertEquals(Error(msg, 1)) assertEquals(Error(msg, 0)) assertEquals(Loading, awaitItem()) } } ``` -------------------------------- ### ItemListStateMachineFactory with Custom Conditions Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/8_condition.md This factory demonstrates how to use custom conditions within `inState` blocks to manage state transitions. It includes handling the loading state and an error state with a countdown timer. ```kotlin class ItemListStateMachineFactory( private val httpClient: HttpClient ) : FlowReduxStateMachineFactory() { init { initializeWith { ListState( loading = true, items = emptyList(), error = null, errorCountdown = null ) } spec { inState { condition({ state -> state.loading == true }) { onEnter { // We entered the loading state, so let's do the HTTP request try { val items = httpClient.loadItems() mutate { this.copy(loading = false, items = items, error = null, errorCountdown = null) } } catch (t: Throwable) { mutate { this.copy( loading = false, items = emptyList(), error = "A network error occurred", errorCountdown = 3, ) } } } } condition({ state -> state.error != null }) { on { action : RetryLoadingAction -> mutate { this.copy(loading = true, items = emptyList(), error = null, errorCountdown = null) } } val timer : Flow = timerThatEmitsEverySecond() collectWhileInState(timer) { value : Int -> mutate { if (errorCountdown!! > 0) // decrease the countdown by 1 second this.copy(errorCountdown = this.errorCountdown!! - 1) else // transition to Loading this.copy( loading = true, items = emptyList(), error = null, errorCountdown = null ) } } } } } } } ``` -------------------------------- ### Applying Execution Policies to FlowRedux Components Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/11_ExecutionPolicy.md Demonstrates how to apply different execution policies to various FlowRedux components like on, onActionEffect, and collectWhileInState. Note that onEnter does not support execution policies. ```kotlin on(executionPolicy = ExecutionPolicy.CancelPrevious) { ... } ``` ```kotlin onActionEffect(executionPolicy = ExecutionPolicy.CancelPrevious) { ... } ``` ```kotlin collectWhileInState(executionPolicy = ExecutionPolicy.CancelPrevious) { ... } ``` ```kotlin collectWhileInStateEffect(executionPolicy = ExecutionPolicy.CancelPrevious) { ... } ``` -------------------------------- ### FavoriteStatusStateMachine Implementation Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/13_composing-statemachines.md Implements a state machine to handle toggling an item's favorite status and saving changes to a server. It includes states for `OperationInProgress` and `OperationFailed`, with a delay for error recovery. ```kotlin class FavoriteStatusStateMachine( item : Item, private val httpClient : HttpClient ) : FlowReduxStateMachineFactory() { // doesn't handle Action, thus we can use Nothing init { initializeWith { OperationInProgress( itemId = item.itemId, markAsFavorite = item.favoriteStatus is NotFavorite ) } spec { inState{ onEnter { toggleFavoriteAndSaveToServer() } } inState{ onEnter{ waitFor3SecondsThenResetToOriginalState() } } } } private suspend fun ChangeableState.toggleFavoriteAndSaveToServer() : ChangedState { return try { val itemId = state.snapshot.itemId val markAsFavorite = state.snapshot.markAsFavorite httpClient.toggleFavorite( itemId = itemId, markAsFavorite = markAsFavorite // if false then unmark it, if true mark it as favorite ) if (markAsFavorite) { override { Favorite(itemId) } } else { override { NotFavorite(itemId) } } } catch (exception : Throwable) { state.override { OperationFailed(itemId, markAsFavorite) } } } private suspend fun ChangeableState.waitFor3SecondsThenResetToOriginalState() : ChangedState { delay(3_000) // Wait for 3 seconds val itemId = state.snapshot.itemId val markAsFavorite = state.snapshot.markAsFavorite return if (markAsFavorite) { // marking as favorite failed, // thus original status was "not marked as favorite" override { NotFavorite(itemId) } } else { override { Favorite(itemId) } } } } ``` -------------------------------- ### Run Ktlint Lint Checker Source: https://github.com/freeletics/flowredux/blob/main/docs/contributing.md Use this command to check for linting errors without auto-formatting. The CI also runs this check. ```shell # This runs the lint checker ./kotlinw scripts/ktlint.main.kts --fail-on-changes ``` -------------------------------- ### Introduce Intermediate Interface for Subset States Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/10_accross-multiple-states.md Introduce an intermediate sealed interface to group a subset of states. This allows you to define actions that apply only to this specific group, such as states related to post-loading content. ```kotlin sealed interface ListState { // Shows a loading indicator on screen object Loading : ListState sealed interface PostLoading : ListState // List of items loaded successfully, show it on screen data class ShowContent(val items: List) : PostLoading // Error while loading happened data class Error(val message: String) : PostLoading } ``` -------------------------------- ### Define Actions for State Machine Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/1_basics.md Defines the possible actions that can be dispatched to the state machine, such as retrying the loading process. ```kotlin sealed interface Action { object RetryLoadingAction : Action } ``` -------------------------------- ### Unordered Execution Policy for Actions Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/11_ExecutionPolicy.md Use the Unordered policy when you want all action handlers to run, regardless of order, without canceling previous executions. This is useful when parallel execution is acceptable and order is not critical. ```kotlin spec { inState { on(executionPolicy = ExecutionPolicy.Unordered) { delay(randomInt()) // wait for some random time override { OtherState } } } } ``` -------------------------------- ### Define List State for State Machine Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/1_basics.md Defines the possible states for a list loading feature: Loading, ShowContent with items, and Error with a message. ```kotlin sealed interface ListState { // Shows a loading indicator on screen object Loading : ListState // List of items loaded successfully, show it on screen data class ShowContent(val items: List) : ListState // Error while loading happened data class Error(val message: String) : ListState } ``` -------------------------------- ### Define InboxState and SelectedEmail data classes Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/9_untilIdentityChanged.md These data classes represent the state of the inbox, including a list of emails and the currently selected email with its details. ```kotlin data class InboxState( val emails : List, val selectedEmail : SelectedEmail?, ) data class SelectedEmail( val emailId : Int, val details : EmailDetails?, ) ``` -------------------------------- ### Define Item and FavoriteStatus Data Classes Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/13_composing-statemachines.md Defines the data structures for `Item` and its `FavoriteStatus`, including states for not favorite, favorite, operation in progress, and operation failed. This models the UI and backend communication requirements. ```kotlin data class Item(val id : Int, val name : String) ``` ```kotlin data class Item( val id : Int, val name : String, val favoriteStatus : FavoriteStatus ) sealed interface FavoriteStatus { val itemId : Int // It is not marked as favorite yet data class NotFavorite(override val itemId : Int) : FavoriteStatus // Marked as favorites data class Favorite(override val itemId : Int) : FavoriteStatus // An operation (read: HTTP request) is in progress to either mark // it as favorite or not mark it as favorite data class OperationInProgress( override val itemId : Int, val markAsFavorite : Boolean // true means mark as favorite, false means unmark it ) : FavoriteStatus // The operation (read: HTTP request) to either mark it as favorite // or unmark it as favorite has failed; so did not succeed. data class OperationFailed( override val itemId : Int, val markAsFavorite : Boolean // true means mark as favorite, false means unmark it ) : FavoriteStatus } ``` -------------------------------- ### Define FlowRedux State Machine Source: https://github.com/freeletics/flowredux/blob/main/README.md Define states, actions, and transitions for a state machine. Use `onEnter` for entry logic, `on` for action handling, and `collectWhileInState` for observing flows. ```kotlin sealed interface State object Loading : State data class ContentState(val items : List) : State data class Error(val error : Throwable) : State sealed interface Action object RetryLoadingAction : Action class MyStateMachine : FlowReduxStateMachine(initialState = Loading){ init { spec { inState { onEnter { state : State -> // executes this block whenever we enter Loading state try { val items = loadItems() // suspending function / coroutine to load items state.override { ContentState(items) } // Transition to ContentState } catch (t : Throwable) { state.override { Error(t) } // Transition to Error state } } } inState { on { action : RetryLoadingAction, state : State -> // executes this block whenever Error state is current state and RetryLoadingAction is emitted state.override { Loading } // Transition to Loading state which loads list again } } inState { collectWhileInState( flowOf(1,2,3) ) { value : Int, state : State -> // observes the given flow as long as state is ContentState. // Once state is changed to another state the flow will automatically // stop emitting. state.mutate { copy( items = this.items + Item("New item $value")) } } } } } } ``` -------------------------------- ### Define State Machine with inState Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/2_inState.md Use inState to specify that code within the block should only execute when the state machine is in the given state. This is a fundamental part of defining state-specific logic. ```kotlin class ItemListStateMachineFactory( private val httpClient: HttpClient ) : FlowReduxStateMachineFactory { init { spec { initializeWith { Loading } inState { // ... } } } } ``` -------------------------------- ### Produce StateMachine in Compose Source: https://github.com/freeletics/flowredux/blob/main/docs/compose.md Use `produceStateMachine()` to create a FlowRedux state machine whose state is exposed as a Compose `State`. This allows for direct observation and recomposition of UI elements when the state changes. ```kotlin val stateMachineFactory = AddressBookStateMachineFactory() @Composable fun AddressBookUi() { // Extension function that is provided by FlowRedux where the `state` of the // created `StateMachine` is a Compose `State`. val stateMachine = stateMachineFactory.produceStateMachine() val state = stateMachine.state.value Column { SearchBoxUi(state.searchQuery, dispatch) } LazyColumn { items(state.contacts) { contact : Contact -> ContactUi(contact, stateMachine::dispatch) } } } ``` -------------------------------- ### Add Multiplatform Dependencies Source: https://github.com/freeletics/flowredux/blob/main/README.md Use the `flowredux` artifact for multiplatform projects targeting JVM, iOS, watchOS, tvOS, macOS, Linux, and Windows. ```groovy implementation 'com.freeletics.flowredux:flowredux:' ``` -------------------------------- ### Test Retry Action from Error State Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/14_testing.md Check that dispatching `RetryLoadingAction` from the `Error` state correctly transitions the state machine back to the `Loading` state. This test utilizes `initializeWith` to set a custom initial state. ```kotlin import kotlinx.coroutines.test.runTest @Test fun `from Error state to Loading if RetryLoadingAction is dispatched`() = runTest { val initialState = Error(message = "A network error occurred", countdown = 3) val factory = ItemListStateMachineFactory(httpClient) factory.initializeWith { initialState } val stateMachine = factory.shareIn(backgroundScope) stateMachine.state.test { assertEquals(initialState, awaitItem()) // now we dispatch the retry action stateMachine.dispatch(RetryLoadingAction) // next state should then be Loading assertEquals(Loading, awaitItem()) } } ``` -------------------------------- ### Add JVM/Android Dependencies Source: https://github.com/freeletics/flowredux/blob/main/README.md Include the core `flowredux-jvm` artifact and the `compose` extensions for Jetpack Compose integration in your Android or JVM project. ```groovy implementation 'com.freeletics.flowredux:flowredux-jvm:' implementation 'com.freeletics.flowredux:compose:' ``` -------------------------------- ### Define onEnter Action in Loading State Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/3_onEnter.md Use onEnter to perform an HTTP request when entering the Loading state. Handle success by overriding with ShowContent or failure by overriding with Error. ```kotlin class ItemListStateMachineFactory( private val httpClient: HttpClient ) : FlowReduxStateMachineFactory() { init { initializeWith { Loading } spec { inState { onEnter { // we entered the Loading state, // so let's do the HTTP request try { val items = httpClient.loadItems() // loadItems() is a suspend function override { ShowContent(items) } // return ShowContent from onEnter block } catch (t: Throwable) { override { Error("A network error occurred") } // return Error state from onEnter block } } } } } } ``` -------------------------------- ### Interact with FlowRedux State Machine Source: https://github.com/freeletics/flowredux/blob/main/README.md Launch coroutines to collect state changes and dispatch actions to the state machine. Ensure coroutines are launched within an appropriate scope. ```kotlin val statemachine = MyStateMachine() launch { // Launch a coroutine statemachine.state.collect { state -> // do something with new state like update UI renderUI(state) } } // emit an Action launch { // Launch a coroutine statemachine.dispatch(action) } ``` -------------------------------- ### FlowRedux State Machine API Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/1_basics.md The public API of a `FlowReduxStateMachine` includes a `state` Flow for observing state changes and a `dispatch` function for sending actions. ```kotlin class FlowReduxStateMachine { val state : Flow suspend fun dispatch(action : Action) } ``` -------------------------------- ### Reacting to RetryLoadingAction in Error State Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/5_onAction.md This snippet demonstrates how to handle a `RetryLoadingAction` when the state machine is in the `Error` state. It transitions the state machine back to the `Loading` state, which will re-initiate the item loading process. ```kotlin class ItemListStateMachineFactory( private val httpClient: HttpClient ) : FlowReduxStateMachineFactory() { init { initializeWith { Loading } spec { inState { onEnter { state: State -> // We have discussed this block already in a previous section try { val items = httpClient.loadItems() state.override { ShowContent(items) } } catch (t: Throwable) { state.override { Error("A network error occurred") } } } } // let's add a new inState{...} with an on{...} block inState { on { action: RetryLoadingAction -> // This block triggers if we are in Error state and // RetryLoadingAction has been dispatched to this state machine. // In that case we transition to Loading state which then starts the HTTP // request to load items again as the inState + onEnter { ... } triggers override { Loading } } } } } } ``` -------------------------------- ### Using mutate() to Update State Properties Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/4_State-ChangedState.md Illustrates the appropriate use of mutate() to update a specific property (visitCounter) within the ScreenStatisticsState, while remaining in the same state type. Using override() for this purpose is discouraged due to readability. ```kotlin data class ScreenStatisticsState( val name : String, val visitCounter : Int ) spec { inState { onEnter { mutate { this.copy(visitCounter= this.visitCounter + 1) } // OK: just update a property but stay in ScreenStatisticsState } } inState { onEnter { state.override { copy(visitCounter= this.visitCounter + 1) // compiles but hard to read } } } } ``` -------------------------------- ### FlowRedux State Machine with Effects Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/7_effects.md Demonstrates the usage of `onEnterEffect`, `onActionEffect`, and `collectWhileInStateEffect` within a FlowRedux state machine. These effects are triggered on state entry, specific action dispatch, or flow collection, respectively, and are intended for side operations like logging or analytics. ```kotlin class ItemListStateMachineFactory : FlowReduxStateMachineFactory() { init { initializeWith { Loading } spec { inState { onEnterEffect { logMessage("Did enter $snapshot") // Note: there is no state change } onActionEffect { action : RetryLoadingAction -> // current state can be accessed through snapshot analyticsTracker.track(ButtonClickedEvent()) // Note: there is no state change } val someFlow : Flow = TODO() collectWhileInStateEffect(someFlow) { value : String -> logMessage("Collected $value from flow while in state $snapshot") // Note: there is no state change } } } } } ``` -------------------------------- ### Refactor State Machine Logic to Functions Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/12_improve-readability.md Extract complex state transitions and side effects into private functions. This keeps the main state machine specification concise and focused on the flow. ```kotlin class ItemListStateMachineFactory( private val httpClient: HttpClient ) : FlowReduxStateMachineFactory() { // This is the specification of your state machine. // Less implementation details, better readability. init { initializeWith { Loading // if creating the initial state would require multiple lines it could be moved to a function as well } spec { inState { onEnter { loadItemsAndMoveToContentOrError() } } inState { on { action -> // For a single-line statement it's OK to keep logic inside the block instead // of extracting it to a function (but it also depends on your testing strategy) state.override { Loading } } collectWhileInState(timerThatEmitsEverySecond()) { value -> decrementCountdownAndMoveToLoading(value) } } } } // // All the implementation details are in the functions below. // private suspend fun ChangeableState.loadItemsAndMoveToContentOrError(): ChangedState { return try { val items = httpClient.loadItems() override { ShowContent(items) } } catch (t: Throwable) { override { Error(cause = t, countdown = 3) } } } private fun ChangeableState.decrementCountdownAndMoveToLoading( value: Int, ): ChangedState { return override { if (this.countdownTimeLeft > 0) this.copy(countdown = countdownTimeLeft - 1) else { Loading } } } private fun timerThatEmitsEverySecond(): Flow = flow { var timeElapsed = 0 while (isActive) { delay(1000) timeElapsed++ emit(timeElapsed) } } } ``` -------------------------------- ### Dispatch Actions from Compose UI Source: https://github.com/freeletics/flowredux/blob/main/docs/compose.md In Jetpack Compose, dispatch actions to a FlowRedux state machine asynchronously using a provided dispatch function. This is commonly used in event handlers like `onValueChange` for text fields. ```kotlin @Composable fun SearchBoxUi(searchQuery : String, dispatchAction: (AddressBookAction) -> Unit) { Column { TextField( value = searchQuery, // Dispatches an action asynchronously to the state machine onValueChange = { text -> dispatchAction(SearchQueryChangedAction(text)) } ) } } ``` -------------------------------- ### Data Class for List State Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/8_condition.md This data class represents a possible state for the `ItemListStateMachineFactory`. Note that using sealed classes is the recommended practice for state modeling. ```kotlin data class ListState( val loading: Boolean, // true means loading, false means not loading val items: List, // empty list if no items loaded yet val error: String?, // if not null we are in error state val errorCountdown: Int? // the seconds for the error countdown ) ``` -------------------------------- ### Define Sealed Interface for List States Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/10_accross-multiple-states.md Define a sealed interface to represent different states of a list, such as Loading, ShowContent, and Error. This structure is fundamental for state management. ```kotlin sealed interface ListState object Loading : ListState data class ShowContent : ListState data class Error (val message : String) : ListState ``` -------------------------------- ### Define Error State with Countdown Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/6_collectWhileInState.md Extend the existing state to include a countdown property for managing timed operations. ```kotlin data class Error( val message: String, val countdown: Int // This value is decreased from 3 then 2 then 1 and represents the countdown value. ) : ListState ``` -------------------------------- ### Define ToggleFavoriteItemAction Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/13_composing-statemachines.md Defines a simple action class `ToggleFavoriteItemAction` used to trigger the favorite status change for a specific item. ```kotlin data class ToggleFavoriteItemAction(val itemId : Int) : Action ``` -------------------------------- ### Implement Timed Retry Logic with collectWhileInState Source: https://github.com/freeletics/flowredux/blob/main/docs/user-guide/6_collectWhileInState.md Use collectWhileInState to trigger actions based on a timer while in the Error state. The flow is automatically canceled when the state changes. ```kotlin class ItemListStateMachineFactory( private val httpClient: HttpClient ) : FlowReduxStateMachineFactory() { init { initializeWith { Loading } spec { inState { onEnter { try { val items = httpClient.loadItems() override { ShowContent(items) } } catch (t: Throwable) { override { Error( message = "A network error occurred", countdown = 3 // countdown is new ) } } } } inState { on { action: RetryLoadingAction -> override { Loading } } val timer : Flow = timerThatEmitsEverySecond() collectWhileInState(timer) { timerValue: Int -> override { if (this.countdown > 0) { this.copy(countdown = this.countdown - 1) } else { Loading } } } } } } private fun timerThatEmitsEverySecond(): Flow = flow { var timeElapsed = 0 while (isActive) { delay(1_000) timeElapsed++ emit(timeElapsed) } } } ``` -------------------------------- ### Integrate FlowRedux with Android ViewModel Source: https://github.com/freeletics/flowredux/blob/main/README.md Use FlowRedux within an Android ViewModel by collecting state updates and dispatching actions using `viewModelScope.launch`. This ensures proper lifecycle management. ```kotlin class MyViewModel @Inject constructor(private val stateMachine : MyStateMachine) : ViewModel() { val state = MutableLiveData() init { viewModelScope.launch { // automatically canceled once ViewModel lifecycle reached destroyed. stateMachine.state.collect { newState -> state.value = newState } } } fun dispatch(action : Action) { viewModelScope.launch { stateMachine.dispatch(action) } } } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.