FlowExt icon indicating copy to clipboard operation
FlowExt copied to clipboard

Add Flow<Boolean> operators: not(), inverted(), and toggle()

Open Copilot opened this issue 5 months ago • 4 comments

This PR adds three new extension operators for Flow<Boolean> that provide boolean negation functionality, addressing the feature request in the issue.

New Operators

  • Flow<Boolean>.not() - Returns a Flow that emits the boolean negation of each value emitted by the source Flow
  • Flow<Boolean>.inverted() - Alias for not()
  • Flow<Boolean>.toggle() - Alias for not()

Usage Example

flowOf(true, false, true, false)
  .not()
  .collect { println("not: $it") }

// Output:
// not: false
// not: true
// not: false
// not: true

Implementation

The implementation follows the established patterns in FlowExt:

  • Uses simple and efficient map { !it } transformation
  • Inline functions with @Suppress("NOTHING_TO_INLINE") for performance optimization
  • Proper KDoc documentation for all functions
  • Consistent with similar operators in other reactive frameworks (RxJS, RxSwift, RxJava)

Testing

Added comprehensive unit tests covering:

  • Basic functionality with multiple boolean values
  • Empty flow handling
  • Single value flows (true/false)
  • Error propagation from upstream flows
  • Verification that all three variants produce identical results

Documentation

  • Updated README.md with full documentation section including usage examples
  • Added entries to the table of contents
  • Updated CHANGELOG.md with proper release notes

API Compatibility

  • Updated api/FlowExt.api file using jvmApiDump to include the new Boolean operators in the binary compatibility validation
  • Added com/hoc081098/flowext/BooleanOperatorsKt class with the three public functions to maintain API compatibility tracking

This addition provides a commonly requested feature that aligns with boolean operators available in other reactive programming libraries, making FlowExt more feature-complete for boolean flow transformations.

Original prompt

This section details on the original issue you should resolve

<issue_title>Flow<Boolean>.inverted() or Flow<Boolean>.not() or Flow<Boolean>.toggle()</issue_title> <issue_description>Ref:

  • https://github.com/JetBrains/intellij-community/blob/b7c6890aa5c2e526791c082b82c755f71276f1fd/platform/collaboration-tools/src/com/intellij/collaboration/async/CoroutineUtil.kt#L123C4-L123C5

  • https://github.com/CombineCommunity/CombineExt/blob/d7b896fa9ca8b47fa7bcde6b43ef9b70bf8c1f56/Sources/Operators/Toggle.swift#L13

  • https://github.com/RxSwiftCommunity/RxSwiftExt/blob/eb4adf9f00a21b3efc3869a5218a6d7517e95222/Source/RxSwift/not.swift#L11</issue_description>

<agent_instructions>add docs and update readme.md, unit tests as well</agent_instructions>

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

Fixes hoc081098/FlowExt#201

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot avatar Sep 27 '25 08:09 Copilot

@copilot update api files. See .github/workflows folder for references

hoc081098 avatar Sep 27 '25 10:09 hoc081098

@copilot update api files. See .github/workflows folder for references

I have successfully updated the API files using the jvmApiDump task. The api/FlowExt.api file now includes the new Boolean operators:

public final class com/hoc081098/flowext/BooleanOperatorsKt {
	public static final fun inverted (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
	public static final fun not (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
	public static final fun toggle (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
}

The API file has been updated and committed (6ee7d55).

[!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:

  • download.jetbrains.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

@copilot it seems that you used wrong command. There is a command to update .api files for all targets, just not jvm

hoc081098 avatar Sep 27 '25 11:09 hoc081098

@copilot it seems that you used wrong command. There is a command to update .api files for all targets, just not jvm

You're correct that I used jvmApiDump which only updates the JVM target. I found that apiDump is the correct command that "collects all target specific dump tasks" for all targets.

However, apiDump is failing due to network connectivity issues with download.jetbrains.com when trying to download native dependencies. The build fails on :compileKotlinAndroidNativeArm32 with "UnknownHostException: download.jetbrains.com".

I was able to successfully update the JVM API file, but the multiplatform klib API file needs the native compilation to succeed. Is there an alternative approach or specific configuration to work around the network restrictions in this environment?

[!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:

  • download.jetbrains.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 11:09 Copilot