Skip to content

Latest commit

 

History

History
271 lines (180 loc) · 10.7 KB

18-rxjava.md

File metadata and controls

271 lines (180 loc) · 10.7 KB

RxJava

⬅ Back to index

Covers: reactive basics, Observable types, Schedulers, operators, Subjects, hot vs cold, backpressure, disposal, error handling, migration to Flow. (Still asked often for legacy/large codebases.)


1. What is RxJava? What is reactive programming?

Answer

Reactive programming means programming with asynchronous data streams: you declare how data flows and transforms, and you react to events as they arrive (Observer pattern + iterator + functional operators).

RxJava is the JVM implementation of ReactiveX. Core parts: Observable (source) → Operators (transform) → Observer/Subscriber (consumer), with Schedulers for threading and Disposables for cleanup. RxAndroid adds AndroidSchedulers.mainThread().

2. What are the types of Observables?

Answer
Type Emits Use case Coroutine equivalent
Observable 0..N items, no backpressure UI events, small streams Flow
Flowable 0..N items, with backpressure Large/fast streams (DB, sensors) Flow
Single exactly 1 item or error Network call suspend fun(): T
Maybe 0 or 1 item, or error Cache lookup suspend fun(): T?
Completable no item; completes or errors Save/delete operation suspend fun()

3. What is the Observer contract?

Answer

onSubscribe(Disposable) → onNext(item)* → then either onComplete() or onError(Throwable). Nothing comes after a terminal event. Single uses onSuccess and onError.

4. subscribeOn vs observeOn?

Answer
  • subscribeOn(Schedulers.io()) – chooses the thread where the source starts (where the subscription happens). Its position in the chain doesn't matter, and only the first one counts.
  • observeOn(AndroidSchedulers.mainThread()) – switches the thread for every operator downstream of it. You can use it multiple times.
api.getUsers()
    .subscribeOn(Schedulers.io())
    .map { it.toDomain() }
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe({ render(it) }, { showError(it) })

5. What are Schedulers?

Answer
  • Schedulers.io() – unbounded cached pool for I/O.
  • Schedulers.computation() – fixed pool (one thread per CPU core) for CPU work.
  • Schedulers.newThread(), Schedulers.single(), Schedulers.trampoline() (current thread, queued; useful in tests), Schedulers.from(executor).
  • AndroidSchedulers.mainThread() (RxAndroid).

6. map vs flatMap vs concatMap vs switchMap?

Answer
  • map – transforms each item 1:1, synchronously.
  • flatMap – maps each item to an Observable and merges the results. Runs concurrently, order not guaranteed.
  • concatMap – like flatMap but sequential: waits for each inner Observable, so order is preserved.
  • switchMap – cancels the previous inner Observable when a new item arrives. Ideal for search-as-you-type.

7. zip vs merge vs combineLatest vs concat?

Answer
  • zip – pairs items by index from multiple sources (wait for both API calls, then combine).
  • merge – interleaves emissions from sources as they arrive.
  • concat – subscribes to the sources one after another (cache first, then network).
  • combineLatest – emits whenever any source emits, using the latest from each (form validation).

8. What are Subjects? Explain their types.

Answer

A Subject is both an Observable and an Observer (hot). It's used to bridge imperative code into Rx.

  • PublishSubject – emits only items received after subscription.
  • BehaviorSubject – emits the latest item (or a default) on subscribe, then the following ones. Like StateFlow.
  • ReplaySubject – replays all (or N) previous items to new subscribers.
  • AsyncSubject – emits only the last value, on completion.

Relays (RxRelay) are Subjects that can't terminate, which is safer for UI events.

9. Hot vs cold Observables?

Answer
  • Cold – starts producing per subscriber (each subscription re-runs the source, e.g. a Retrofit call).
  • Hot – produces regardless of subscribers, and they share the emissions (Subjects, UI clicks).
  • Turn cold into hot with publish().refCount(), share(), replay(1).autoConnect().

10. What is backpressure? How does Flowable handle it?

Answer

Backpressure is the situation where the producer emits faster than the consumer can handle. An Observable has no backpressure support, so it risks OutOfMemoryError. A Flowable uses request-based flow control with strategies: BackpressureStrategy.BUFFER, DROP, LATEST, ERROR, MISSING, or operators like onBackpressureDrop().

11. What are Disposable and CompositeDisposable? Why are they needed?

Answer

subscribe() returns a Disposable. Calling dispose() unsubscribes and stops the work. If you don't dispose, the subscription keeps the Activity alive, which causes memory leaks and callbacks on dead views.

private val disposables = CompositeDisposable()
disposables += api.getUsers().subscribe(...)
override fun onCleared() { disposables.clear() }   // in ViewModel

clear() disposes everything and lets you reuse the container; dispose() also makes it reject future additions.

12. How do you handle errors in RxJava?

Answer
  • onErrorReturn { default }, onErrorReturnItem(x).
  • onErrorResumeNext { fallbackObservable }.
  • retry(3), retryWhen { errors -> ... } (backoff).
  • doOnError { log(it) } for side effects only.
  • Always provide an onError in subscribe. Otherwise you get an OnErrorNotImplementedException crash.
  • Set RxJavaPlugins.setErrorHandler for undeliverable exceptions (errors after disposal).

13. What are debounce, throttleFirst, distinctUntilChanged?

Answer
  • debounce(300ms) – emits only after a quiet period (search input).
  • throttleFirst(1s) – emits the first item, then ignores the rest for a window (prevents double clicks).
  • distinctUntilChanged() – skips consecutive duplicates.

14. What are doOnNext, doOnSubscribe, doFinally?

Answer

Side-effect operators that don't change the stream: logging, showing or hiding a loader (doOnSubscribe { showLoading() }, doFinally { hideLoading() }).

15. How do you test RxJava code?

Answer
  • TestObserver: observable.test().assertValues(a, b).assertComplete().
  • Replace schedulers: RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() } and RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() } (usually in a JUnit rule).
  • TestScheduler for controlling time (advanceTimeBy).

16. RxJava vs Coroutines/Flow – how do you migrate?

Answer
RxJava Coroutines
Single<T> suspend fun(): T
Completable suspend fun()
Observable/Flowable Flow<T>
BehaviorSubject MutableStateFlow
PublishSubject MutableSharedFlow
subscribeOn/observeOn flowOn / withContext
CompositeDisposable Structured concurrency (scope cancellation)
switchMap flatMapLatest

Migrate incrementally, layer by layer with kotlinx-coroutines-rx3 (.await(), .asFlow(), rxSingle {}, asObservable()). Start with the data layer or new features.

17. just vs fromCallable vs create vs defer?

Answer
  • just(x) – emits a value that's already computed (evaluated eagerly when the Observable is built, even if nobody subscribes). Observable.just(db.query()) runs the query immediately, possibly on the main thread.
  • fromCallable { } – computes the value lazily on subscribe, on the subscribeOn thread. Exceptions become onError. The best choice for wrapping a single blocking call.
  • create { emitter -> } – full manual control: emit many values, complete or fail, wrap listener/callback APIs, and register cleanup with emitter.setCancellable { }. You must respect the Observable contract yourself.
  • defer { Observable.just(x) } – creates a fresh Observable for each subscriber at subscribe time, so each subscriber gets current data. It's useful to make an eager source lazy.

18. How are timer, delay and interval used?

Answer
  • Observable.timer(2, SECONDS) – emits a single 0L after the delay, then completes (one-shot delayed actions, splash timeouts).
  • .delay(2, SECONDS) – an operator that shifts each emission of an existing stream by the given time. (Errors aren't delayed by default.)
  • Observable.interval(1, SECONDS) – emits 0, 1, 2… repeatedly at a fixed period (polling, countdowns; combine with take(n)). Dispose it, or it runs forever.

All of them run on Schedulers.computation() by default. Coroutine equivalents: delay(), and loops with delay inside flow {}.

19. How do you make parallel network calls and implement search with RxJava?

Answer
// Parallel calls → combine
Single.zip(api.getProfile().subscribeOn(Schedulers.io()),
           api.getFeed().subscribeOn(Schedulers.io())) { profile, feed -> Home(profile, feed) }
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(::render, ::showError)
    .addTo(disposables)

// Instant search
searchSubject
    .debounce(300, TimeUnit.MILLISECONDS)
    .map { it.trim() }
    .filter { it.isNotEmpty() }
    .distinctUntilChanged()
    .switchMapSingle { q -> api.search(q).subscribeOn(Schedulers.io()).onErrorReturnItem(emptyList()) }
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(::showResults)

Each source needs its own subscribeOn(io()); otherwise zip subscribes to them sequentially on the same thread. switchMap cancels stale searches. Errors are handled inside the inner stream so the search stream survives.


⬅ Previous: Threading & Background Work · ⬅ Back to index · Next: Performance & Memory ➡