Commit b2f19348 authored by MozLando's avatar MozLando
Browse files

Merge #4327



4327: Flow.ifChanged(): Added variant that takes a mapping function. r=csadilek a=pocmo

This pattern emerged while migrating components. Often I want listen to value changes, but if the value changes then I need more than only the changed value.

One example is the downloads feature. I want to listen to download changes. But if that changes then I need the whole `SessionState` object.

Previously:

```kotlin
scope = store.flowScoped { flow ->
    flow.mapNotNull { state -> state.findCustomTabOrSelectedTab(customTabId) }
           .map { it.content.download }
           .ifChanged()
          .collect { download ->
              // Okay, the DownloadState object now. But I also want the enclosing SessionState.
          }
}
```

Now:

```kotlin
scope = store.flowScoped { flow ->
    flow.mapNotNull { state -> state.findCustomTabOrSelectedTab(customTabId) }
        .ifChanged { it.content.download }
        .collect { state ->
            // Yay, I have the whole session state and can get
            // to the download by using state.content.download
        }
}
```
Co-authored-by: default avatarSebastian Kaspari <s.kaspari@gmail.com>
parents a49e4410 15d0aee2
......@@ -12,16 +12,33 @@ import kotlinx.coroutines.flow.filter
* the value emitted before them.
*
* Example:
* ```
* Original Flow: A, B, B, C, A, A, A, D, A
* Returned Flow: A, B, C, A, D, A
* ```
*/
fun <T> Flow<T>.ifChanged(): Flow<T> {
fun <T> Flow<T>.ifChanged(): Flow<T> = ifChanged { it }
/**
* Returns a [Flow] containing only values of the original [Flow] where the result of calling
* [transform] has changed from the result of the previous value.
*
* Example:
* ```
* Block: x -> x[0] // Map to first character of input
* Original Flow: "banana", "bus", "apple", "big", "coconut", "circle", "home"
* Mapped: b, b, a, b, c, c, h
* Returned Flow: "banana", "apple", "big", "coconut", "home"
* ``
*/
fun <T, R> Flow<T>.ifChanged(transform: (T) -> R): Flow<T> {
var observedValueOnce = false
var lastValue: T? = null
var lastMappedValue: R? = null
return filter { value ->
if (!observedValueOnce || value !== lastValue) {
lastValue = value
val mapped = transform(value)
if (!observedValueOnce || mapped !== lastMappedValue) {
lastMappedValue = mapped
observedValueOnce = true
true
} else {
......
......@@ -21,4 +21,18 @@ class FlowKtTest {
listOf("A", "B", "C", "A", "D", "A"),
items)
}
@Test
fun `ifChanged operator with block`() {
val originalFlow = flowOf("banana", "bus", "apple", "big", "coconut", "circle", "home")
val items = runBlocking {
originalFlow.ifChanged { item -> item[0] }.toList()
}
assertEquals(
listOf("banana", "apple", "big", "coconut", "home"),
items
)
}
}
......@@ -66,6 +66,9 @@ permalink: /changelog/
* **feature-push**
* Added more logging into `AutoPushFeature` to aid in debugging in release builds.
* **support-ktx**
* Added variant of `Flow.ifChanged()` that takes a mapping function in order to filter items where the mapped value has not changed.
# 11.0.0
* [Commits](https://github.com/mozilla-mobile/android-components/compare/v10.0.0...v11.0.0)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment