Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ dependencies {
- [`retryWithExponentialBackoff`](#retrywithexponentialbackoff)
- [`scanWith`](#scanWith)
- [`select`](#select)
- [`selectAsStateFlow`](#selectasstateflow)
- [`skipUntil`](#skipuntil--dropuntil)
- [`dropUntil`](#skipuntil--dropuntil)
- [`takeUntil`](#takeuntil)
Expand Down Expand Up @@ -1418,6 +1419,53 @@ select: [b]

----

#### selectAsStateFlow

- Combines the memoized selector functionality of [select](#select) with StateFlow conversion.
- Creates a hot, stateful flow that caches the latest selected value using [StateFlow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-state-flow/).
- Perfect for UI state management where you need both the efficiency of memoized selectors and the benefits of StateFlow.

```kotlin
// Example with coroutine scope
val scope = CoroutineScope(Dispatchers.Default)

val stateFlow = MutableStateFlow(
UiState(
items = listOf("a", "b", "c"),
term = "a",
isLoading = false
)
)

// Create a StateFlow that selects and filters items
val filteredItemsStateFlow = stateFlow.selectAsStateFlow(
scope = scope,
started = SharingStarted.Eagerly,
initialValue = emptyList<String>(),
selector1 = { it.items },
selector2 = { it.term },
projector = { items, term ->
items.filter { it.contains(term ?: "", ignoreCase = true) }
}
)

// Access current value immediately
println("Current filtered items: ${filteredItemsStateFlow.value}")

// Collect updates
filteredItemsStateFlow.collect { items ->
println("Filtered items updated: $items")
}
```

Benefits:
- **Memoized computation**: Selectors are only recomputed when input sub-states change
- **Hot StateFlow**: Always has the latest value available via `.value`
- **Configurable sharing**: Control when the StateFlow starts/stops with `SharingStarted`
- **Multiple overloads**: Support for 1-5 selectors with projector functions

----

#### skipUntil / dropUntil

- ReactiveX docs: https://reactivex.io/documentation/operators/skipuntil.html
Expand Down
159 changes: 159 additions & 0 deletions src/commonMain/kotlin/com/hoc081098/flowext/selectAsStateFlow.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* MIT License
*
* Copyright (c) 2021-2024 Petrus Nguyễn Thái Học
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package com.hoc081098.flowext

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn

/**
* Select a sub-state from the [State] and convert it to a [StateFlow].
*
* This function combines the memoized selector functionality with StateFlow conversion,
* providing a hot, stateful flow that caches the latest selected value.
*
* @param scope The [CoroutineScope] in which sharing is started.
* @param started The strategy that controls when sharing is started and stopped.
* @param initialValue The initial value of the StateFlow.
* @param selector A function that takes the [State] and returns a sub-state.
*/
public fun <State, Result> Flow<State>.selectAsStateFlow(
scope: CoroutineScope,
started: SharingStarted = SharingStarted.Eagerly,
initialValue: Result,
selector: Selector<State, Result>,
): StateFlow<Result> = select(selector).stateIn(scope, started, initialValue)

/**
* Select two sub-states from the source [Flow], combine them into a [Result], and convert to a [StateFlow].
*
* The [projector] will be invoked only when one of the sub-states is changed.
* The returned StateFlow will emit the result of [projector],
* and all subsequent results repetitions of the same value are filtered out.
*
* @param scope The [CoroutineScope] in which sharing is started.
* @param started The strategy that controls when sharing is started and stopped.
* @param initialValue The initial value of the StateFlow.
* @param selector1 The first selector to be used to select first sub-states.
* @param selector2 The second selector to be used to select second sub-states.
* @param projector The projector to be used to combine the sub-states into a result.
*/
public fun <State, SubState1, SubState2, Result> Flow<State>.selectAsStateFlow(
scope: CoroutineScope,
started: SharingStarted = SharingStarted.Eagerly,
initialValue: Result,
selector1: Selector<State, SubState1>,
selector2: Selector<State, SubState2>,
projector: suspend (subState1: SubState1, subState2: SubState2) -> Result,
): StateFlow<Result> = select(selector1, selector2, projector).stateIn(scope, started, initialValue)

/**
* Select three sub-states from the source [Flow], combine them into a [Result], and convert to a [StateFlow].
*
* The [projector] will be invoked only when one of the sub-states is changed.
* The returned StateFlow will emit the result of [projector],
* and all subsequent results repetitions of the same value are filtered out.
*
* @param scope The [CoroutineScope] in which sharing is started and stopped.
* @param started The strategy that controls when sharing is started and stopped.
* @param initialValue The initial value of the StateFlow.
* @param selector1 The first selector to be used to select first sub-states.
* @param selector2 The second selector to be used to select second sub-states.
* @param selector3 The third selector to be used to select third sub-states.
* @param projector The projector to be used to combine the sub-states into a result.
*/
public fun <State, SubState1, SubState2, SubState3, Result> Flow<State>.selectAsStateFlow(
scope: CoroutineScope,
started: SharingStarted = SharingStarted.Eagerly,
initialValue: Result,
selector1: Selector<State, SubState1>,
selector2: Selector<State, SubState2>,
selector3: Selector<State, SubState3>,
projector: suspend (subState1: SubState1, subState2: SubState2, subState3: SubState3) -> Result,
): StateFlow<Result> = select(selector1, selector2, selector3, projector).stateIn(scope, started, initialValue)

/**
* Select four sub-states from the source [Flow], combine them into a [Result], and convert to a [StateFlow].
*
* The [projector] will be invoked only when one of the sub-states is changed.
* The returned StateFlow will emit the result of [projector],
* and all subsequent results repetitions of the same value are filtered out.
*
* @param scope The [CoroutineScope] in which sharing is started and stopped.
* @param started The strategy that controls when sharing is started and stopped.
* @param initialValue The initial value of the StateFlow.
* @param selector1 The first selector to be used to select first sub-states.
* @param selector2 The second selector to be used to select second sub-states.
* @param selector3 The third selector to be used to select third sub-states.
* @param selector4 The fourth selector to be used to select fourth sub-states.
* @param projector The projector to be used to combine the sub-states into a result.
*/
public fun <State, SubState1, SubState2, SubState3, SubState4, Result> Flow<State>.selectAsStateFlow(
scope: CoroutineScope,
started: SharingStarted = SharingStarted.Eagerly,
initialValue: Result,
selector1: Selector<State, SubState1>,
selector2: Selector<State, SubState2>,
selector3: Selector<State, SubState3>,
selector4: Selector<State, SubState4>,
projector: suspend (subState1: SubState1, subState2: SubState2, subState3: SubState3, subState4: SubState4) -> Result,
): StateFlow<Result> = select(selector1, selector2, selector3, selector4, projector).stateIn(scope, started, initialValue)

/**
* Select five sub-states from the source [Flow], combine them into a [Result], and convert to a [StateFlow].
*
* The [projector] will be invoked only when one of the sub-states is changed.
* The returned StateFlow will emit the result of [projector],
* and all subsequent results repetitions of the same value are filtered out.
*
* @param scope The [CoroutineScope] in which sharing is started and stopped.
* @param started The strategy that controls when sharing is started and stopped.
* @param initialValue The initial value of the StateFlow.
* @param selector1 The first selector to be used to select first sub-states.
* @param selector2 The second selector to be used to select second sub-states.
* @param selector3 The third selector to be used to select third sub-states.
* @param selector4 The fourth selector to be used to select fourth sub-states.
* @param selector5 The fifth selector to be used to select fifth sub-states.
* @param projector The projector to be used to combine the sub-states into a result.
*/
public fun <State, SubState1, SubState2, SubState3, SubState4, SubState5, Result> Flow<State>.selectAsStateFlow(
scope: CoroutineScope,
started: SharingStarted = SharingStarted.Eagerly,
initialValue: Result,
selector1: Selector<State, SubState1>,
selector2: Selector<State, SubState2>,
selector3: Selector<State, SubState3>,
selector4: Selector<State, SubState4>,
selector5: Selector<State, SubState5>,
projector: suspend (
subState1: SubState1,
subState2: SubState2,
subState3: SubState3,
subState4: SubState4,
subState5: SubState5,
) -> Result,
): StateFlow<Result> = select(selector1, selector2, selector3, selector4, selector5, projector).stateIn(scope, started, initialValue)
Loading