{"id":16838871,"url":"https://github.com/akarnokd/kotlin-flow-extensions","last_synced_at":"2025-05-15T20:03:37.796Z","repository":{"id":35392156,"uuid":"198768969","full_name":"akarnokd/kotlin-flow-extensions","owner":"akarnokd","description":"Extensions to the Kotlin Flow library.","archived":false,"fork":false,"pushed_at":"2025-05-14T19:19:04.000Z","size":341,"stargazers_count":510,"open_issues_count":1,"forks_count":20,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-05-14T20:31:23.341Z","etag":null,"topics":["async","continuation","coroutines","extensions","flow","functional","kotlin"],"latest_commit_sha":null,"homepage":null,"language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/akarnokd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-07-25T06:13:29.000Z","updated_at":"2025-05-14T19:19:00.000Z","dependencies_parsed_at":"2023-11-24T08:31:26.617Z","dependency_job_id":"75df0921-96b9-4a1d-9fe2-4a78a34d35d0","html_url":"https://github.com/akarnokd/kotlin-flow-extensions","commit_stats":{"total_commits":156,"total_committers":5,"mean_commits":31.2,"dds":0.5961538461538461,"last_synced_commit":"92541390cc849268921eb877966a1669ce78b61c"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akarnokd%2Fkotlin-flow-extensions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akarnokd%2Fkotlin-flow-extensions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akarnokd%2Fkotlin-flow-extensions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akarnokd%2Fkotlin-flow-extensions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/akarnokd","download_url":"https://codeload.github.com/akarnokd/kotlin-flow-extensions/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254414493,"owners_count":22067271,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","continuation","coroutines","extensions","flow","functional","kotlin"],"created_at":"2024-10-13T12:26:57.817Z","updated_at":"2025-05-15T20:03:36.262Z","avatar_url":"https://github.com/akarnokd.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# kotlin-flow-extensions\nExtensions to the Kotlin Flow library.\n\n\u003ca href='https://github.com/akarnokd/kotlin-flow-extensions/actions?query=workflow%3A%22Java+CI+with+Gradle%22'\u003e\u003cimg src='https://github.com/akarnokd/kotlin-flow-extensions/workflows/Java%20CI%20with%20Gradle/badge.svg'\u003e\u003c/a\u003e\n[![codecov.io](http://codecov.io/github/akarnokd/kotlin-flow-extensions/coverage.svg?branch=master)](http://codecov.io/github/akarnokd/kotlin-flow-extensions?branch=master)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.akarnokd/kotlin-flow-extensions/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.akarnokd/kotlin-flow-extensions)\n\n## dependency\n\n[Maven](https://search.maven.org/search?q=com.github.akarnokd)\n\n```groovy\ndependencies {\n    implementation \"com.github.akarnokd:kotlin-flow-extensions:0.0.14\"\n}\n```\n\n# Features\n\nTable of contents\n\n- Hot Flows\n  - [PublishSubject](#publishsubject)\n  - [ReplaySubject](#replaysubject)\n  - [BehaviorSubject](#behaviorsubject)\n  - [UnicastSubject](#unicastsubject)\n  - [UnicastWorkSubject](#unicastworksubject)\n- Sources\n  - `range`\n  - `timer`\n  - [`concatArrayEager`](#concatarrayeager)\n- Intermediate Flow operators (`FlowExtensions`)\n  - `Flow.concatWith`\n  - `Flow.groupBy`\n  - `Flow.parallel`\n  - [`Flow.publish`](#flowpublish)\n  - `Flow.replay`\n  - `Flow.startCollectOn`\n  - `Flow.takeUntil`\n  - `Flow.onBackpressureDrop`\n  - [`Flow.flatMapDrop`](#flowflatmapdrop)\n  - [`Flow.concatMapEager`](#flowconcatmapeager)\n  - [`Flow.amb`](#flowamb)\n- `ParallelFlow` operators (`FlowExtensions`)\n  - `ParallelFlow.concatMap`\n  - `ParallelFlow.filter`\n  - `ParallelFlow.map`\n  - `ParallelFlow.reduce`\n  - `ParallelFlow.sequential`\n  - `ParallelFlow.transform`\n- `ConnectableFlow`\n  \n## PublishSubject\n\nMulticasts values to one or more flow collectors in a coordinated fashion, awaiting each collector to be ready\nto receive the next item or termination.\n\n```kotlin\nimport hu.akarnokd.kotlin.flow.*\n\nrunBlocking {\n    \n    val publishSubject = PublishSubject\u003cInt\u003e()\n\n    val job = launch(Dispatchers.IO) {\n        publishSubject.collect {\n            println(it)\n        }\n        println(\"Done\")\n    }\n    \n    // wait for the collector to arrive\n    while (!publishSubject.hasCollectors()) {\n        delay(1)\n    }\n\n   \n    publishSubject.emit(1)\n    publishSubject.complete()\n   \n    job.join()\n}\n```\n\n## ReplaySubject\n\nCaches and replays some or all items to collectors. Constructors for size-bound, time-bound and both size-and-time bound\nreplays are available. An additional constructor with a `TimeUnit -\u003e Long` has been defined to allow virtualizing\nthe progression of time for testing purposes\n\n```kotlin\nimport hu.akarnokd.kotlin.flow.*\n\nrunBlocking {\n    \n    val replaySubject = ReplaySubject\u003cInt\u003e()\n\n    val job = launch(Dispatchers.IO) {\n        replaySubject.collect {\n            println(it)\n        }\n        println(\"Done\")\n    }\n   \n    // wait for the collector to arrive\n    while (!replaySubject.hasCollectors()) {\n        delay(1)\n    }\n\n    replaySubject.emit(1)\n    replaySubject.emit(2)\n    replaySubject.emit(3)\n    replaySubject.complete()\n   \n    job.join()\n\n    replaySubject.collect {\n        println(it)\n    }\n    println(\"Done 2\")\n}\n```\n\n## BehaviorSubject\n\nCaches the last item received and multicasts it and subsequent items (continuously) to collectors, awaiting each collector to be ready\nto receive the next item or termination. It is possible to set an initial value to be sent to fresh collectors via a constructor.\n\n```kotlin\nimport hu.akarnokd.kotlin.flow.*\n\nrunBlocking {\n    \n    val behaviorSubject = BehaviorSubject\u003cInt\u003e()\n    behaviorSubject.emit(1)\n  \n    // OR\n    // val behaviorSubject = BehaviorSubject\u003cInt\u003e(1)\n\n\n    val job = launch(Dispatchers.IO) {\n        behaviorSubject.collect {\n            println(it)\n        }\n        println(\"Done\")\n    }\n   \n    // wait for the collector to arrive\n    while (!behaviorSubject.hasCollectors()) {\n        delay(1)\n    }\n\n    behaviorSubject.emit(2)\n    behaviorSubject.emit(3)\n    behaviorSubject.complete()\n   \n    job.join()\n}\n```\n\n## Flow.flatMapDrop\n\nMaps the upstream value into a `Flow` and relays its items while ignoring further upstream items until the current\ninner `Flow` completes.\n\n```kotlin\nimport hu.akarnokd.kotlin.flow.*\n\nrange(1, 10)\n.map {\n    delay(100)\n    it\n}\n.flatMapDrop {\n    range(it * 100, 5)\n            .map {\n                delay(30)\n                it\n            }\n}\n.assertResult(\n        100, 101, 102, 103, 104,\n        300, 301, 302, 303, 304,\n        500, 501, 502, 503, 504,\n        700, 701, 702, 703, 704,\n        900, 901, 902, 903, 904\n)\n```\n\n## Flow.publish\n\nShares a single connection to the upstream source which can be consumed by many collectors inside a `transform` function,\nwhich then yields the resulting items for the downstream.\n\nEffectively, one collector to the output `Flow\u003cR\u003e` will trigger exactly one collection of the upstream `Flow\u003cT\u003e`. Inside\nthe `transformer` function though, the presented `Flow\u003cT\u003e` can be collected as many times as needed; it won't trigger\nnew collections towards the upstream but share items to all inner collectors as they become available.\n\nUnfortunately, the suspending nature of coroutines/`Flow` doesn't give a clear indication when the `transformer` chain\nhas been properly established, which can result in item loss or run-to-completion without any item being collected.\nIf the number of the inner collectors inside `transformer` can be known, the `publish(expectedCollectors)` overload\ncan be used to hold back the upstream until the expected number of collectors have started/ready collecting items.\n\n#### Example:\n\n```kotlin\n    range(1, 5)\n    .publish(2) { \n         shared -\u003e merge(shared.filter { it % 2 == 0 }, shared.filter { it % 2 != 0 }) \n    }\n    .assertResult(1, 2, 3, 4, 5)\n```\n\nIn the example, it is known `merge` will establish 2 collectors, thus the `publish` can be instructed to await those 2.\nWithout the argument, `range` would rush through its items as `merge` doesn't start collecting in time, causing an\nempty result list.\n\n## UnicastSubject\n\nBuffers items until a single collector starts collecting items. Use `collectorCancelled` to\ndetect when the collector no longer wants to collect items.\n\nNote that the subject uses an unbounded inner buffer and does not suspend its input side if\nthe collector never arrives or can't keep up.\n\n```kotlin\nval us = UnicastSubject()\n\nlaunchIn(Dispatchers.IO) {\n    for (i in 1..200) {\n        println(\"Emitting $i\")\n        us.emit(i)\n        delay(1)\n    }\n    emit.complete()\n}\n\n// collector arrives late for some reason\ndelay(100)\n\nus.collect { println(\"Collecting $it\") }\n```\n\n## UnicastWorkSubject\n\nBuffers items until and inbetween a single collector is able to collect items. If the current\ncollector cancels, the next collector will receive the subsequent items.\n\nNote that the subject uses an unbounded inner buffer and does not suspend its input side if\nthe collector never arrives or can't keep up.\n\n```kotlin\nval uws = UnicastWorkSubject()\n\ngenerateInts(uws, 1, 15)\n\n// prints lines 1..5\nuws.take(5).collect { println(it) }\n\n// prints lines 6..10\nuws.take(5).collect { println(it) }\n\n// prints lines 11..15\nuws.take(5).collect { println(it) }\n```\n\n## concatArrayEager\n\nLaunches all at once and emits all items from a source before items of the next are emitted.\n\nFor example, given two sources, if the first is slow, the items of the second won't be emitted until the first has\nfinished emitting its items. This operators allows all sources to generate items in parallel but then still emit those\nitems in the order their respective `Flow`s are listed.\n\nNote that each source is consumed in an unbounded manner and thus, depending on the speed of\nthe current source and the collector, the operator may retain items longer and may use more memory\nduring its execution.\n\n```kotlin\nconcatArrayEager(\n        range(1, 5).onStart { delay(200) },\n        range(6, 5)\n)\n.assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)\n```\n\n## Flow.concatMapEager\n\nMaps the upstream values into [Flow]s and launches them all at once, then\nemits items from a source before items of the next are emitted.\n\nFor example, given two inner sources, if the first is slow, the items of the second won't be emitted until the first has\nfinished emitting its items. This operators allows all sources to generate items in parallel but then still emit those\nitems in the order their respective `Flow`s are mapped in.\n\nNote that the upstream and each source is consumed in an unbounded manner and thus,\ndepending on the speed of the current source and the collector, the operator may retain\nitems longer and may use more memory during its execution.\n\n```kotlin\nrange(1, 5)\n.concatMapEager {\n    range(it * 10, 5).onEach { delay(100) }\n}\n.assertResult(\n        10, 11, 12, 13, 14,\n        20, 21, 22, 23, 24,\n        30, 31, 32, 33, 34,\n        40, 41, 42, 43, 44,\n        50, 51, 52, 53, 54\n)\n```\n\n## Flow.amb\n\nStarts collecting all source [Flow]s and relays the items of the first one to emit an item,\ncancelling the rest.\n\n```kotlin\namb(\n    range(1, 5).onStart { delay(1000) },\n    range(6, 5).onStart { delay(100) }\n)\n.assertResult(6, 7, 8, 9, 10)\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakarnokd%2Fkotlin-flow-extensions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fakarnokd%2Fkotlin-flow-extensions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakarnokd%2Fkotlin-flow-extensions/lists"}