rxjava-migration
/rxjava-migration
Use only when the user explicitly requests migration from RxJava to Kotlin coroutines and/or flows.
- Author
- rcosteira79 ↗
- Repository
- android-skills ↗
- Licence
- MIT
- Last repository change
- 19.09.2026
Assessment and security
- Files in this package
- 2
- Scripts
- 0
- HTML comments
- 0
- URLs in SKILL.md
- 0
No signals detected. Repository changed on 19.09.2026, without references to retired models or APIs.
MIT, open: use and modify while retaining author attribution.
What it does and when to use it
Use only when the user explicitly requests migration from RxJava to Kotlin coroutines and/or flows.
Original author description
Use only when the user explicitly requests migration from RxJava to Kotlin coroutines and/or flows.
How it works
- Step 1: Complexity gate
- Type mapping (state it explicitly — don't silently transform)
- Retry — the accuracy trap
- Interop during incremental migration
- Complex cases — stop and ask before migrating
SKILL.md preview
---
name: rxjava-migration
description: Use only when the user explicitly requests migration from RxJava to Kotlin coroutines and/or flows.
---
# RxJava Migration
Migrate RxJava to Kotlin coroutines/flows incrementally. **Only invoke this skill when the user explicitly asks to migrate RxJava code.** Simple cases map directly; complex cases need a strategy and user input *before* any code is written.
## Step 1: Complexity gate
Classify before writing any migrated code. A chain is **Complex** if ANY of: nested `flatMap`/`switchMap` with an inner `Single`/`Observable`; a custom `Scheduler`; complex error recovery (`retryWhen`, backoff, retry counts); multi-source `zip`/`combineLatest` (3+ sources); `Flowable` with an explicit backpressure strategy; a `Subject` shared across classes; or an unclear API return type. **Any single Complex criterion makes the whole chain Complex** — a two-operator chain with `retryWhen` is Complex.
**Always confirm what each called API returns before migrating — do not assume.** A leaf you think is migrated may still return `Single`/`Observable`.
## Type mapping (state it explicitly — don't silently transform)
| RxJava | Coroutines/Flow |
|---|---|
| `Observable<T>` / `Flowable<T>` | `Flow<T>` (Flowable: match the original backpressure with `buffer()` / `conflate()`) |
| `Single<T>` / `Maybe<T>` / `Completable` | `suspend fun`: `T` / `T?` / `Unit` |
| `PublishSubject` / `ReplaySubject(n)` | `MutableSharedFlow(replay = 0 / n)` |
| `BehaviorSubject<T>` | **ask** — `MutableStateFlow` vs `MutableSharedFlow(replay = 1)` |
**`BehaviorSubject` — ask first:** `StateFlow` always has a current value (needs an initial, exposes `.value`, replays it to new collectors); `SharedFlow(replay = 1)` also replays the last emission but has no `.value` and no initial-value requirement. *"Do you always have an initial value and need `.value` access, or is the stream sometimes empty at start?"*
### Schedulers and the `observeOn` shift
`Schedulers.io()` → `Dispatchers.IO`, `computation()` → `Default`, `mainThread()` → `Main`, `single()` → `newSingleThreadContext(...)`, `newThread()` → `IO` (per-task thread spawning isn't idiomatic in coroutines).
- **`observeOn(AndroidSchedulers.mainThread())` does NOT become a dispatcher switch** in the repository/use case — it means the **caller** collects on `Main`, which in the coroutines model is the ViewModel's job (`viewModelScope` runs on `Main`). Explain this shift to the developer.
- **When the called API is already a `suspend fun`**, it manages its own dispatcher via `withContext` — `subscribeOn` has no equivalent and simply disappears. **Do not wrap an already-main-safe suspend function in another `withContext`.**
- `subscribeOn` + `observeOn` → `flowOn` applies **upstream only**; restructure accordingly.
For the full operator mapping, see `migration-map.md`.
## Retry — the accuracy trap
RxJava's `retryWhen` is stateful; Flow's `retry` predicate receives the **`Throwable`**, not an index.
```kotlin
// WRONG — 'attempt' is a Throwable, not a Long; this does not compile
flow.retry(3) { attempt -> delay(attempt * 1000L); true }
// CORRECT — retry(n)'s predicate gets the cause
flow.retry(3) { cause -> cause is IOException }
// CORRECT — stateful backoff: attempt IS the 0-based index here
flow.retryWhen { cause, attempt ->
if (cause is IOException && attempt < 3) { delay((attempt + 1) * 1000L); true } else false
}
```
Use `retryWhen { cause, attempt -> }` for any policy that depends on the attempt count.
## Interop during incremental migration
Add `kotlinx-coroutines-rx3` (or `rx2`); keep interop **at layer boundaries only**, never mixing RxJava and coroutines inside one function body.
```kotlin
observable.asFlow(); single.await(); maybe.awaitSingleOrNull(); completable.await() // Rx → coroutines
flow.asObservable(); flow.asSingle() // coroutines → Rx (asSingle throws if the flow emits 0 or 2+ elements)
```
Migrate leaf-up (data source → repository → use case → ViewModel), remove each bridge once its layer is fully migrated, and commit per layer.
## Complex cases — stop and ask before migrating
`retryWhen` (count? linear vs exponential? which error types? what after exhaustion?); a custom `Scheduler` (which `CoroutineDispatcher`?); `Flowable` backpressure (`buffer` / `conflate` / `DROP_OLDEST`?); `flatMap`/`switchMap` over **writes** — `switchMap` → `flatMapLatest` is safe for reads (search/live data) but cancels in-flight work, so it's dangerous for writes; a `Subject` shared across classes (state → `StateFlow`, event → `SharedFlow`?). For `onErrorResumeNext` → `catch`, rethrow cancellation: `catch { e -> if (e is CancellationException) throw e else emit(fallback) }`.
(`compositeDisposable.clear()` in `onCleared()` → delete it entirely: `viewModelScope` is cancelled automatically when the ViewModel is cleared.)
Files in this package (2)
- SKILL.md 5 KB
- migration-map.md 4 KB
Versions
The database has no other skills named rxjava-migration.
Database snapshot: 13.11.2026 · package version 30ca5ef64d4a (SHA-256 digest of the entire package)
Downloads in the last 30 days: 0 · ranking
Report a problem with this skill: harmful content, incorrect descriptions or an author’s removal request.
Similar skills
- 100rxjava-to-coroutines-migration
Guide and execute the migration of asynchronous code from RxJava to Kotlin Coroutines and Flow. Use this skill when a user asks to convert RxJava (Observables, Singles, Completables, Subjects) to Coroutines (suspend functions, Flows, StateFlows). - 100kotlin-coroutines-flows
Kotlin Coroutines and Flow patterns for Android and KMP — structured concurrency, Flow operators, StateFlow, error handling, and testing. Use when writing coroutines or Flow code on Android or KMP, or debugging cancellation and concurrency. - 100kotlin-tooling-java-to-kotlin
Use when converting Java source files to idiomatic Kotlin, when user mentions "java to kotlin", "j2k", "convert java", "migrate java to kotlin", or when working with .java files that need to become .kt files. Handles framework-aware conversion for Spring, Lombok, Hibernate,… - 100kotlin-expert
Expert-level Kotlin development, Android, coroutines, and multiplatform. Use when the user mentions Android, coroutines, multiplatform, or JVM, or when the task involves Kotlin Fundamentals, Android Development, Kotlin Multiplatform, or Kotlin Style. - 85Kotlin Coroutines
Use when kotlin coroutines for structured concurrency including suspend functions, coroutine builders, Flow, channels, and patterns for building efficient asynchronous code with cancellation and exception handling. - 90android-kotlin-coroutines
Use when implementing async operations with Kotlin coroutines, Flow, StateFlow, or managing concurrency in Android apps.