Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion resultflow/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
## Unreleased

- *No changes*
### Breaking changes

- `resultFlow(block: suspend () -> Result<T>)` is no longer available from source code.
In 0.1.0 the two `resultFlow` overloads had ambiguous resolution: a block returning `Result<T>` could end up single- or double-wrapped depending on how it was typed.

The overload is now hidden (`DeprecationLevel.HIDDEN` + `@JvmSynthetic`), so `resultFlow(block: suspend () -> T)` is the only source-visible overload; exceptions thrown from `block` are caught and wrapped into `Result.failure`.

Because the `Result<T>` overload is hidden, a block that already returns `Result<T>` now binds to the raw-value overload and produces a double-wrapped `Flow<Result<Result<T>>>` — there is **no** compile-time error, so migrate such call sites explicitly:
```kotlin
// Before (0.1.0) — emitted the Result as-is:
resultFlow { repository.fetchData() } // fetchData(): Result<Data>

// After — unwrap the Result inside the block (failure is caught and re-wrapped):
resultFlow { repository.fetchData().getOrThrow() }

// Or build the flow manually to emit the Result as-is (without catching):
flow { emit(repository.fetchData()) }
```
- The overload is now `@JvmSynthetic`, so it is also hidden from Java source. Its `@JvmName("resultFlowResult")` is retained, so code already compiled against 0.1.0 keeps linking against it and behaves as before — the break is source-level only.

## [0.1.0] (2024-08-01)

Expand Down
17 changes: 14 additions & 3 deletions resultflow/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,27 @@ dependencies {

## Usage

Use `resultFlow` function to turn long operations into `Flow<Result<T>>`:
Use `resultFlow` to turn a long operation that returns a raw value (and may throw) into `Flow<Result<T>>`. Exceptions thrown from the block are caught and emitted as `Result.failure`:

```kotlin
resultFlow { respository.fetchData() }
// repository.fetchData(): Data (may throw)
resultFlow { repository.fetchData() }
```

If your operation **already returns `Result<T>`**, unwrap it inside the block — otherwise it binds to the raw-value overload above and is silently double-wrapped into `Flow<Result<Result<T>>>` (there is no compile-time error for this):

```kotlin
// repository.fetchData(): Result<Data>
resultFlow { repository.fetchData().getOrThrow() }

// Or, if you need to emit the pre-built Result as-is without catching:
flow { emit(repository.fetchData()) }
```

Use `foldEach` to map result value or handle both `Success` and `Failure`:

```kotlin
resultFlow { respository.fetchData() }
resultFlow { repository.fetchData() }
.foldEach(
onSuccess = { handleContent(it) },
onFailure = { showError(it) },
Expand Down
34 changes: 27 additions & 7 deletions resultflow/src/main/kotlin/ResultFlow.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,41 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map

/**
* Creates a flow containing a single value – the result returned from the given [block].
* Creates a flow containing a single value – the result of the given [block] wrapped into [Result].
* Any exception thrown from [block] is caught and emitted as [Result.failure].
*
* If [block] itself returns `Result<T>`, wrapping it again would produce a double-wrapped
* `Flow<Result<Result<T>>>`. Unwrap the [Result] inside the block (e.g. `block().getOrThrow()`)
* so its failure is caught as above, or build the flow manually via [flow] to emit a pre-built
* [Result] as-is.
*
* @see flow
* @see toResultFlow
*/
@JvmName("resultFlowResult")
public fun <T> resultFlow(block: suspend () -> Result<T>): Flow<Result<T>> {
Comment thread
TopHlop marked this conversation as resolved.
public inline fun <T> resultFlow(crossinline block: suspend () -> T): Flow<Result<T>> {
return flow { emit(block()) }
.toResultFlow()
}

/**
* Creates a flow containing a single value – the result of the given [block] wrapped into [Result].
* @see flow
* Bytecode-only overload for a [block] that already returns `Result<T>`: it emits the pre-built
* [Result] as-is, yielding `Flow<Result<T>>` (single-wrapped, with the failure left un-caught)
* instead of the double-wrapped `Flow<Result<Result<T>>>` the raw-value overload would produce.
*
* It is hidden from Kotlin sources via [DeprecationLevel.HIDDEN] and from Java via [JvmSynthetic],
* so it is not a candidate for new callers; it stays in the bytecode only to keep already-compiled
* code working. New code should unwrap the [Result] inside the block or use `flow { emit(block()) }`.
*/
public fun <T> resultFlow(block: suspend () -> T): Flow<Result<T>> {
@Deprecated(
"Block returning Result<T> would produce Flow<Result<Result<T>>>. " +
"Unwrap the Result inside the block (e.g., block().getOrThrow()), " +
"or use `flow { emit(block()) }` to emit the Result as-is.",
level = DeprecationLevel.HIDDEN,
)
@JvmName("resultFlowResult")
@JvmSynthetic
public fun <T> resultFlow(block: suspend () -> Result<T>): Flow<Result<T>> {
return flow { emit(block()) }
.toResultFlow()
}

/** Wraps values and errors from [this] flow with [Result]. */
Expand Down
Loading