Muhammad Ahmed AbuTalib
Muhammad Ahmed AbuTalib

Reputation: 4302

RxJava2 Single.Concat for repository pattern

I am using Room with RxJava2 to implement my data layer via Repository Pattern principles.

I have the following simple code which decides where to pick data from.

@Override
    public Single<Team> getTeamById(int teamId) {
        return Single.
                concat(local.getTeamById(teamId),
                        remote.getTeamById(teamId)).
                filter(team -> team != null).
                firstOrError();
    }

The problem here is that instead of going to the remote source , it returns an error from the first source (local) if the data was not available.

android.arch.persistence.room.EmptyResultSetException: Query returned empty result set: select * from teams where id = ?

How should I instruct the concat to forgo any error that is received and continue its concatenation?

Upvotes: 4

Views: 4189

Answers (4)

muminers
muminers

Reputation: 1210

You cannot pass null in RxJava2. So whenever your local repo is empty you just can't return null in your single. There was a question o stack about handling null objects: Handle null in RxJava2

Also here you can find an article showing you preferred implementation of repository pattern using RxJava2: https://android.jlelse.eu/rxjava-2-single-concat-sample-for-repository-pattern-1873c456227a

So simplifying - instead of returning null from both local and remote repo pass some sort of "empty" object. That will be useful also in your business logic allowing you to recognize empty set of data.

Upvotes: 3

Bin Fan
Bin Fan

Reputation: 51

I used Maybe to solve my Rxjava2 repository pattern problem.

In your case, I would use the following code to sort it out:

//you may need to rewrite your local.getTeamById method
protected Maybe<Team> getTeamById(int teamId) {
    Team team = localDataHelper.getTeamById(teamId);
    return team != null ? Maybe.just(team) : Maybe.empty();
}

@Override
public Single<Team> getTeamById(int teamId) {
    Maybe<Team> cacheObservable = local.getTeamById(teamId);
    Maybe<Team> apiCallObservable = remote.getTeamById(teamId).toMaybe();

    return Maybe.concat(cacheObservable, apiCallObservable)
            .toSingle();
}

Upvotes: 1

akarnokd
akarnokd

Reputation: 69997

If you want to continue when the first source errors (instead of completing as empty), you can use onErrorResumeNext instead of concat (I assume both get calls return Observable, adjust as necessary):

return local.getTeamById(teamId)
   .onErrorResumeNext(error -> {
       if (error instanceof EmptyResultSetException) {
           return remote.getTeamById(teamId));
       }
       return Observable.error(error);
   })
   .firstOrError();

Upvotes: 1

TheWhiteLlama
TheWhiteLlama

Reputation: 1286

Aslong you're not sure if you can receive at least one Team from you data provider, you should probably think of using Maybe instead of Single.

You can lookup the definition here:

Single as it states:

it always either emits one value or an error notification

Use Maybe instead: Maybe

there could be 0 or 1 item or an error signalled by some reactive source

As your error already states there seems to be a problem while extracting results from your query.

Handle your result extraction correctly, so that you check if there are results before trying extracting any. Therefor the Maybe would either return 0 or 1 item, and not throw any error at all when no Team was found.

Upvotes: 3

Related Questions