FlowExt icon indicating copy to clipboard operation
FlowExt copied to clipboard

Implement withLatestFrom for multiple flows (3-10 flows) with optimized Array-based implementation

Open Copilot opened this issue 5 months ago • 2 comments

This PR extends the withLatestFrom operator to support combining multiple flows (3-10 flows), addressing the issue request for "withLatestFrom for many flows".

Changes Made

Core Implementation

  • Added 8 new overloads of withLatestFrom in src/commonMain/kotlin/com/hoc081098/flowext/withLatestFrom.kt supporting 3-10 flows
  • Refactored implementation to eliminate code duplication by introducing a new public Array-based function that all overloads use internally
  • Includes both transform function variants and a convenience overload returning Triple<A, B, C> for 3 flows
  • Proper resource management with coroutineScope and job cancellation

Array-Based Core Function

  • New public function withLatestFrom(others: Array<out Flow<*>>, transform: suspend (A, Array<Any?>) -> R) that serves as the foundation for all multi-flow overloads
  • Uses Array<AtomicRef<Any?>> for efficient thread-safe latest value storage
  • Eliminates code duplication while maintaining the same API and performance characteristics
  • Code reduction: 48% smaller implementation (736 → 382 lines)

API Examples

// 3 flows with Triple return
val result = sourceFlow
  .withLatestFrom(flow2, flow3)
  .collect { (a, b, c) -> println("$a, $b, $c") }

// 4 flows with custom transform
val result = sourceFlow
  .withLatestFrom(flow2, flow3, flow4) { a, b, c, d -> 
    "$a-$b-$c-$d" 
  }

// Up to 10 flows supported
val result = sourceFlow
  .withLatestFrom(f2, f3, f4, f5, f6, f7, f8, f9, f10) { a, b, c, d, e, f, g, h, i, j ->
    // Transform logic here
  }

// Direct Array-based usage (public API)
val result = sourceFlow
  .withLatestFrom(arrayOf(flow2, flow3, flow4)) { value, others ->
    // Custom transformation with others[0], others[1], others[2]
  }

Test Coverage

  • Added comprehensive tests in src/commonTest/kotlin/com/hoc081098/flowext/WithLatestFromTest.kt
  • Tests cover basic functionality, timing semantics, error handling, cancellation, and null values
  • All 259 tests pass, ensuring no regressions

Implementation Details

  • Uses the same INTERNAL_NULL_VALUE pattern for null handling as existing code
  • Follows the exact same concurrency model with launch(start = CoroutineStart.UNDISPATCHED)
  • Consistent with the library's patterns established in combine.kt
  • Proper cleanup of atomic references in finally blocks
  • Maintainable design: All typed overloads delegate to the Array-based function, making future changes easier

The implementation maintains the core semantics of withLatestFrom: the source flow drives emissions, and only emits when all other flows have emitted at least once, using the latest values from each flow.

Original prompt

This section details on the original issue you should resolve

<issue_title>withLatestFrom for many flows</issue_title> <issue_description></issue_description>

<agent_instructions>unit tests, docs, and readme.md</agent_instructions>

Comments on the Issue (you are @copilot in this section)

Fixes hoc081098/FlowExt#184

💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.

Copilot avatar Sep 27 '25 08:09 Copilot

@copilot I see the code pattern is duplicate? Could you introduce a new function that accepts a Array or List of Flows, then 3-10 flows overloads use this function. The new function should be public.

hoc081098 avatar Sep 27 '25 10:09 hoc081098

@copilot I see the code pattern is duplicate? Could you introduce a new function that accepts a Array or List of Flows, then 3-10 flows overloads use this function. The new function should be public.

I've refactored the code to eliminate duplication as requested. Created a new public function that accepts Array<out Flow<*>> and updated all 3-10 flow overloads to use it. This reduced the file from 736 to 382 lines (48% reduction) while maintaining the same API and passing all 259 tests. The changes are in commit b468010.

[!WARNING]

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • dl.google.com
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED -XX:&#43;UseParallelGC -Xmx4g -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant -cp /home/REDACTED/.gradle/wrapper/dists/gradle-9.0.0-bin/d6wjpkvcgsg3oed0qlfss3wgl/gradle-9.0.0/lib/gradle-daemon-main-9.0.0.jar -javaagent:/home/REDACTED/.gradle/wrapper/dists/gradle-9.0.0-bin/d6wjpkvcgsg3oed0qlfss3wgl/gradle-9.0.0/lib/agents/gradle-instrumentation-agent-9.0.0.jar org.gradle.launcher.daemon.bootstrap.GradleDaemon 9.0.0 (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot avatar Sep 27 '25 10:09 Copilot