update claude.md

This commit is contained in:
nrobi144
2025-12-31 07:13:27 +02:00
parent f180fd39e1
commit 65859a9269
9 changed files with 1743 additions and 661 deletions
@@ -0,0 +1,309 @@
# Advanced Flow Operators
Comprehensive guide to Flow operators for complex async patterns in Amethyst.
## Transformation Operators
### flatMapLatest - Cancel Previous, Switch to New
**Use when:** Latest value matters, previous operations should cancel
```kotlin
// User types in search box → cancel previous search
searchQuery
.flatMapLatest { query ->
repository.search(query) // Cancels previous search
}
.collect { results -> updateUI(results) }
```
**Amethyst pattern:**
```kotlin
// Switch relays based on latest account
accountFlow
.flatMapLatest { account ->
relayPool.observeEvents(account.relays)
}
```
### flatMapConcat - Sequential Processing
**Use when:** Order matters, process one at a time
```kotlin
eventIds
.flatMapConcat { id ->
repository.fetchEvent(id)
}
.collect { event -> process(event) }
```
### flatMapMerge - Concurrent Processing
**Use when:** Process multiple simultaneously, order doesn't matter
```kotlin
relays
.flatMapMerge(concurrency = 10) { relay ->
relay.subscribe(filters)
}
.collect { event -> handleEvent(event) }
```
## Combination Operators
### combine - Latest from Multiple Flows
**Use when:** Need latest value from ALL flows
```kotlin
combine(
accountFlow,
settingsFlow,
connectivityFlow
) { account, settings, connectivity ->
AppState(account, settings, connectivity)
}.collect { state -> render(state) }
```
**Pattern:** Re-emits whenever ANY source emits
### zip - Pair Values in Order
**Use when:** Need corresponding values from flows
```kotlin
zip(requestFlow, responseFlow) { req, res ->
Pair(req, res)
}
```
**Pattern:** Waits for BOTH to emit before pairing
### merge - Combine Multiple Flows
**Use when:** Treat multiple flows as single stream
```kotlin
merge(
relay1.events,
relay2.events,
relay3.events
).collect { event -> handleEvent(event) }
```
## Backpressure & Buffering
### shareIn - Hot Flow from Cold
**Use when:** Multiple collectors should share single upstream
```kotlin
val sharedEvents = repository.observeEvents()
.shareIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
replay = 0
)
// Multiple collectors share same upstream
sharedEvents.collect { /* collector 1 */ }
sharedEvents.collect { /* collector 2 */ }
```
**SharingStarted strategies:**
- `Eagerly` - Start immediately, never stop
- `Lazily` - Start on first subscriber, never stop
- `WhileSubscribed(stopTimeout)` - Stop after last unsubscribe + timeout
### stateIn - StateFlow from Cold Flow
**Use when:** Convert Flow to StateFlow (always has value)
```kotlin
val uiState: StateFlow<UiState> = repository.observeData()
.map { data -> UiState.Success(data) }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = UiState.Loading
)
```
**Amethyst pattern:**
```kotlin
// Connectivity status as StateFlow
val connectivity: StateFlow<ConnectivityStatus> =
connectivityFlow.status
.stateIn(
scope = serviceScope,
started = SharingStarted.Eagerly,
initialValue = ConnectivityStatus.Off
)
```
### buffer - Control Backpressure
**Use when:** Producer faster than consumer
```kotlin
eventFlow
.buffer(capacity = 64, onBufferOverflow = BufferOverflow.DROP_OLDEST)
.collect { event -> slowProcessor(event) }
```
**Strategies:**
- `SUSPEND` - Slow down producer (default)
- `DROP_OLDEST` - Drop oldest in buffer
- `DROP_LATEST` - Drop newest emission
### conflate - Keep Only Latest
**Use when:** Only latest value matters, skip intermediate
```kotlin
locationFlow
.conflate() // Skip intermediate locations
.collect { location -> updateMap(location) }
```
## Debouncing & Throttling
### debounce - Wait for Quiet Period
**Use when:** Wait for user to stop typing
```kotlin
searchQuery
.debounce(300) // Wait 300ms after last emission
.flatMapLatest { query -> search(query) }
```
**Amethyst pattern:**
```kotlin
// ConnectivityFlow.kt:87
connectivityFlow
.distinctUntilChanged()
.debounce(200) // Wait 200ms for network to stabilize
.flowOn(Dispatchers.IO)
```
### sample - Periodic Sampling
**Use when:** Rate-limit high-frequency emissions
```kotlin
sensorData
.sample(1000) // Sample every 1 second
.collect { data -> process(data) }
```
## Error Handling
### catch - Handle Upstream Errors
**Use when:** Graceful degradation needed
```kotlin
repository.fetchData()
.catch { e ->
Log.e("Error", e)
emit(emptyList()) // Fallback value
}
.collect { data -> updateUI(data) }
```
**Pattern:** Only catches UPSTREAM errors, not in collect block
### retry/retryWhen - Automatic Retry
```kotlin
relayConnection
.retry(3) { cause ->
cause is IOException // Only retry on network errors
}
```
## Context Switching
### flowOn - Change Upstream Dispatcher
**Use when:** Offload work from current context
```kotlin
repository.fetchData()
.map { heavyProcessing(it) }
.flowOn(Dispatchers.Default) // Heavy work on Default
.collect { updateUI(it) } // Collect on Main
```
**Critical:** Only affects UPSTREAM operators
**Amethyst pattern:**
```kotlin
// ConnectivityFlow.kt:87
callbackFlow { /* ... */ }
.distinctUntilChanged()
.debounce(200)
.flowOn(Dispatchers.IO) // All upstream on IO
```
## Common Patterns
### Pattern: Multi-Relay Subscription
```kotlin
fun observeFromMultipleRelays(relays: List<Relay>, filters: List<Filter>): Flow<Event> =
relays.map { relay ->
relay.subscribe(filters)
}.merge()
.distinctBy { it.id }
```
### Pattern: Load + Cache + Observe
```kotlin
fun observeWithCache(id: String): Flow<Data> = flow {
// Emit cached value immediately
cache[id]?.let { emit(it) }
// Then observe updates
emitAll(repository.observe(id))
}.distinctUntilChanged()
```
### Pattern: Retry with Exponential Backoff
```kotlin
fun <T> Flow<T>.retryWithBackoff(
maxRetries: Int = 3,
initialDelay: Long = 1000
): Flow<T> = retryWhen { cause, attempt ->
if (attempt >= maxRetries || cause !is IOException) {
false
} else {
delay(initialDelay * (1L shl attempt.toInt()))
true
}
}
```
## Performance Tips
1. **Use shareIn for expensive operations**
- Compute once, share with multiple collectors
2. **Choose right backpressure strategy**
- UI updates: `conflate()` or `DROP_OLDEST`
- Events: `buffer()` with appropriate size
3. **flowOn placement matters**
- Place after expensive operators to offload them
4. **Avoid unnecessary emissions**
- Use `distinctUntilChanged()` when appropriate
- Consider `debounce()` for high-frequency sources
5. **StateFlow vs SharedFlow**
- StateFlow: Always has value, conflates
- SharedFlow: Optional replay, configurable buffering