question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Flow] combineTransformLatest()

See original GitHub issue

I’d like to propose introduce new combineTransformLatest operator , which behaves like combineTransform but cancels transformer if new value arrives.

Example: loading places to map; user is moving with map, which cancels the previous fetching, also the loaded data depend on filter or other Flows.

I've implement this
class Symbol(val symbol: String) {
	override fun toString(): String = symbol

	@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
	inline fun <T> unbox(value: Any?): T = if (value === this) null as T else value as T
}

val NULL = Symbol("NULL")

inline fun <reified T, R> combineTransformLatest(
	vararg flows: Flow<T>,
	@BuilderInference crossinline transform: suspend FlowCollector<R>.(Array<T>) -> Unit
): Flow<R> {
	return channelFlow {
		coroutineScope {
			val size = flows.size
			val channels = flows.map { flow ->
				this@coroutineScope.produce<Any> {
					flow.collect { value ->
						this@produce.send(value ?: NULL)
					}
				}
			}
		var job: Job? = null
		val latestValues = arrayOfNulls<Any?>(size)
		val isClosed = Array(size) { false }

		while (!isClosed.all { it }) {
			select<Unit> {
				for (i in 0 until size) {
					if (isClosed[i]) continue
					@Suppress("DEPRECATION")
					channels[i].onReceiveOrNull { receivedValue ->
						if (receivedValue == null) {
							isClosed[i] = true
						} else {
							latestValues[i] = receivedValue
							if (latestValues.all { it !== null }) {
								job?.apply {
									cancel(CancellationException())
									join()
								}
								job = launch {
									val arguments = arrayOfNulls<T>(size)
									for (index in 0 until size) {
										arguments[index] = NULL.unbox(latestValues[index])
									}
									flow<R> {
										@Suppress("UNCHECKED_CAST")
										this@flow.transform(arguments as Array<T>)
									}.collect {
										this@channelFlow.send(it)
									}
								}
							}
						}
					}
				}
			}
		}
	}
}

Not totally sure if the channelFlow as wrapper is correct, probably possible to do it without it but only with access to intenals.

I’m ok to have just this signature - varargs and with transformer, which allows not to emit or emit multiple times in opposite combineLatest().

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:10
  • Comments:6 (5 by maintainers)

github_iconTop GitHub Comments

8reactions
PaulWoitaschekcommented, Sep 27, 2020

I managed to roll my own combineTransformLatest which is based on the existing operators 🎉

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.transformLatest

inline fun <reified T, R> combineTransformLatest(
  vararg flows: Flow<T>,
  @BuilderInference noinline transform: suspend FlowCollector<R>.(Array<T>) -> Unit
): Flow<R> {
  return combine(*flows) { it }
    .transformLatest(transform)
}

fun <T1, T2, R> combineTransformLatest(
  flow: Flow<T1>,
  flow2: Flow<T2>,
  @BuilderInference transform: suspend FlowCollector<R>.(T1, T2) -> Unit
): Flow<R> {
  return combineTransformLatest(flow, flow2) { args ->
    @Suppress("UNCHECKED_CAST")
    transform(
      args[0] as T1,
      args[1] as T2
    )
  }
}
0reactions
Shusekcommented, Dec 24, 2021

In fact, combineTransform should be called combineTransformConcat because any function that is suspended inside transform collector also suspend combines a bit like flatMapConcat operator.

Read more comments on GitHub >

github_iconTop Results From Across the Web

combineTransform - Kotlin
Returns a Flow whose values are generated by transform function that process the most recently emitted values by each flow. The receiver of...
Read more >
Koltin Flow flatMapLatest to combineTransform Using Multiple ...
Use Combine then flatMap latest on the top of that. private val _selectionLocation: MutableStateFlow<Location?> = MutableStateFlow(null) val ...
Read more >
Combining Kotlin Flows with Select Expressions
How do we combine emissions from multiple Flows? We could use operators such as zip, flattenMerge and combine. This article will explore how...
Read more >
How to Combine Kotlin Flows - Better Programming
Kotlin flow is one of the latest and most powerful features of Coroutines. In this article, we're going to learn how to combine...
Read more >
Combining flows: merge, zip, and combine - Kt. Academy
suspend fun main() { val ints: Flow<Int> = flowOf(1, 2, 3) val doubles: Flow<Double> = flowOf(0.1, 0.2, 0.3) ​ val together: Flow<Number> =...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found