使用Room+RxJava2仓库模式时Single.concat本地无数据报错问题求助
Hey there, let's break down why your remote data source isn't kicking in when the local one has no matching team, and fix it up.
The root problem lies with how Single.concat behaves: if the first Single (your local Room query) throws an error, the entire sequence terminates immediately—it never gets to subscribe to the second Single (remote API call). Since Room throws an EmptyResultSetException when a query returns no results, your code bails out right at the local step instead of falling back to remote.
Here are three practical solutions to resolve this:
Solution 1: Catch Local Errors & Convert to Empty Single
Modify your local data source call to catch the "no data" error and convert it into an empty Single. This lets concat move on to the remote source instead of stopping.
@Override public Single<Team> getTeamById(int teamId) { return Single.concat( // Handle local query errors to avoid terminating the sequence local.getTeamById(teamId) .onErrorResumeNext(throwable -> { // Only handle Room's "no data" error; pass other errors through if (throwable instanceof EmptyResultSetException) { return Single.empty(); } return Single.error(throwable); }), remote.getTeamById(teamId) ) .filter(team -> team != null) .firstOrError(); }
Solution 2: Explicit Fallback Logic with flatMap & onErrorResumeNext
This approach makes the "local first, then remote" logic more explicit. We try the local source first—if it returns valid data, we use it; if it fails (no data) or returns null, we fall back to the remote source.
@Override public Single<Team> getTeamById(int teamId) { return Single.defer(() -> local.getTeamById(teamId) .filter(team -> team != null) // Fall back to remote if local has no data or throws an error .onErrorResumeNext(remote.getTeamById(teamId)) ); }
Solution 3: Use Maybe for Local Queries (Cleaner Approach)
Room's Maybe type is perfect for this scenario: it emits a value if data exists, or completes without an error if there's no data. This avoids the error entirely, so concat works as intended.
First, update your DAO to return Maybe<Team> instead of Single<Team>:
@Query("SELECT * FROM teams WHERE id = :teamId") Maybe<Team> getTeamById(int teamId);
Then adjust your repository method to use Maybe.concat:
@Override public Single<Team> getTeamById(int teamId) { return Maybe.concat( local.getTeamById(teamId), remote.getTeamById(teamId).toMaybe() // Convert remote Single to Maybe if needed ) .filter(team -> team != null) .firstOrError(); }
Maybe.concat will subscribe to the next Maybe in the sequence when the previous one completes without emitting a value—exactly what we want for local-to-remote fallback.
Quick Note on Room Return Types
Remember:
Single<Team>throws an error if no data existsMaybe<Team>completes without emitting a value if no data existsFlowable<Team>emits nothing (or continues emitting updates) if no data exists
Using Maybe for single-item queries where data might not exist is usually the most intuitive choice with RxJava.
内容的提问来源于stack exchange,提问作者Muhammad Ahmed AbuTalib

