23

I am a newer on RXJava/RXAndroid. I want to implement this case: chose different way based on some condition in RXJava. For example, first, I fetch user info from network and if this is a VIP user, I will go on to fetch more info from network or just show some info in main thread (break the chain.) Here the flow chart: https://i.stack.imgur.com/0hztR.png

I do some search on this and only find "switchIfEmpty" may help. I write the following code:

getUserFromNetwork("userId")
                .flatMap(new Function<User, ObservableSource<User>>() {
                    @Override
                    public ObservableSource<User> apply(User user) throws Exception {
                        if(!user.isVip){
                            //show user info on MainThread!
                            return Observable.empty();
                        }else{
                            return getVipUserFromNetwork("userId");
                        }
                    }
                }).switchIfEmpty(new ObservableSource<User>() {
                    @Override
                    public void subscribe(Observer<? super User> observer) {
                        //show user info in main thread
                        //just break the chain for normal user
                        observer.onComplete();
                    }
                }).doOnNext(new Consumer<User>() {
                    @Override
                    public void accept(User user) throws Exception {
                        //show vip user info in main thread
                    }
                }).subscribe();

Is there more simple way to achieve this?

Thanks!

niu yi
  • 327
  • 1
  • 2
  • 6

2 Answers2

23

flatMap() is a good choice, you can split the stream with it, but at the end the stream merges together (all emissions from each split observable flows to the main stream). In your code, the switchIfEmpty() is redundant as that's exactly what Observable.empty() do (invoke immediately the onCompleted()), and also you need of course observer on, if you want the display to happen on main thread, but anyhow, I think it's not a good practice to handle this in the middle of the stream.

I think that in your case, you can handle (react to) the user emission in single handler, as it's very similar, just check if that's VIP or not and display it accordingly. so it should look something like this:

getUserFromNetwork("userId")
            .flatMap(new Function<User, ObservableSource<User>>() {
                @Override
                public ObservableSource<User> apply(User user) throws Exception {
                    if (!user.isVip) {
                        return Observable.just(user);
                    } else {
                        return getVipUserFromNetwork("userId");
                    }
                }
            })
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(user -> {
                if (user.isVip){
                    //display vip user
                }else{
                    //display regular user
                }
            });

in this approach you have a single stream flow, without 'side effects' in the middle of the stream.

In case you're handling is totally different (which is not this case), then you can split the stream to 2 separated streams and react to each one differently, this can be done by multicast your getUserFromNetwork() Observable, and from this Observable create 2 different Observable, one that will continue for example getVipUserFromNetwork(), and one that don't, and each can have different subscriber logic. (you can read here my answer regarding multicasting)

Community
  • 1
  • 1
yosriz
  • 10,147
  • 2
  • 24
  • 38
7

I recently found out the switchIfEmpty operator, which suited my needs and may be useful to some. Rx is still a new way of thinking for me, so I'm also open for suggestions and comments. Let me try to give you another way to think about it. As @yosriz pointed out, using switchIfEmpty with a subsequent onComplete is redundant.

As the name says, switchIfEmpty it switch to another observable when the base one completes without emitting any value.

These are the 2 cases:

  • The Observable emits a value and then completes
  • The Observable complete without emitting a value.

The trick is to use an empty stream as your predicate.

Given a base observable used as the predicate, if you filter it's emission, you can chain a switchIfEmpty operator to your fallback stream.

In the following code "User" and "VIP User" share the same interface/class. Even if I'm using Java 8 Lambdas to write the code, notice that there are no IF statements.

  // User Observable, cached so only 1 network call is done
Observable<User> user = getUserFromNetwork("USER_ID").cache();
  // This observable is the user's VIP Status as a boolean stream
Observable<Boolean> isVip = user.map(u -> u.isVip() );

Then we do a bit of logic, we pass downstream the isVip value when he's a VIP, if the user is not a VIP the flatMap will not be evaluated.

Observable<User> vipUser = isVip
    // If VIP emit downstream
    .filter(vip -> vip)
    // This flatmap is ignored if 
    // the emission is filtered out ( vip -> vip == false )
    .flatMap(vip -> user.flatMap(usr -> {
        return getVipUserFromNetwork(usr.getId());
    }));
});

At this point the vipUser observable can

  • Emit a value, which is the flatmapped user
  • Emit nothing and complete

When nothing is emitted the switchIfEmpty will call the other observable

vipUser.switchIfEmpty(user)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(usr -> {
        // Logging the class just to understand the outcome
        System.out.println("User instanceOf " + usr.getClass());
    });

Here's the full code

Observable<User> user = getUserFromNetwork("USER_ID").cache();
Observable<Boolean> isVip = user.map(u -> u.isVip() );

Observable<User> vipUser = isVip
    .filter(vip -> vip)
    .flatMap(vip -> user.flatMap(usr -> {
        return getVipUserFromNetwork(usr.getId());
    }));
});

vipUser.switchIfEmpty(user)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(usr -> {
        // Handle UI Changes
    });
LookForAngular
  • 1,030
  • 8
  • 18