3
votes

I am using RxJava on Android to perform a login operation.

I need to pass in a username, password and a boolean flag. The username and password and sent to a server for verification and once a response is returned I need to use the flag to determine what to do next.

Since the login operation is asynchronous, I want to ensure that when the response returns that I will still have access to the username, password and flag that I passed in at the beginning.

Here is the initial way I coded this up that I believe has problems:

    public Observable<Result> execute1(final String username, final String password, final boolean shouldSaveUsername) {
    return mLoginNetwork
            .loginWithCredentials(username, password)
            .map(new Func1<Response<Void>, LoginObject>() {
                @Override
                public LoginObject call(Response<Void> response) {
                    if (!response.isSuccessful()) {
                        Exceptions.propagate(new HttpException(response));
                    }

                    return new LoginObject(username, password, shouldSaveUsername);
                }
            })
            .doOnNext(new Action1<LoginObject>() {
                @Override
                public void call(LoginObject loginObject) {
                    if (loginObject.shouldSaveUsername) {
                        saveUsername(username);
                    }
                }
            })
            .flatMap(new Func1<Entitlement, Observable<Result>>() {
                @Override
                public Observable<Result> call(LoginObject loginObject) {
                    return mNetwork
                            .fetchSomething();
                }
            });
}

When I call execute1() it returns an Observable which I cache and then subscribe to. If an Android configuration change occurs I unsubscribe from the Observable but keep it in a cache. Once the configuration change is complete I take the Observable out of the cache and resubscribe to it. When I resubscribe the loginWithCredentials call would need to be made again, but when it returns the username, password and boolean flag would no longer exist and therefore I wouldn't be able to use them in my chain which is a problem.

So, how to solve this issue?

I need a way for the input data to the Observable to become part of the Observable so that when I cache the Observable the input data is also cached.

Here is a proposed solution below:

    public Observable<Result> execute2(String username, String password, boolean shouldSaveUsername) {
    return Observable
            .just(new LoginData(username, password, shouldSaveUsername))
            .flatMap(new Func1<LoginData, Observable<LoginData>>() {
                @Override
                public Observable<?> call(final LoginData loginData) {
                    return mLoginNetwork
                            .loginWithCredentials(loginData.getUsername(), loginData.getPassword())
                            .map(new Func1<Response<Void>, LoginData>() {
                                @Override
                                public LoginData call(Response<Void> response) {
                                    if (!response.isSuccessful()) {
                                        Exceptions.propagate(new HttpException(response));
                                    }
                                    return loginData;
                                }
                            });
                }
            })
            .doOnNext(new Action1<LoginData>() {
                @Override
                public void call(LoginData loginData) {
                    if (loginData.shouldSaveUsername) {
                        saveUsername(username);
                    }
                }
            })
            .flatMap(new Func1<LoginData, Observable<Result>>() {
                @Override
                public Observable<Result> call(LoginData loginData) {
                    return mNetwork
                            .fetchSomething();
                }
            });
}

What I'm attempting to do is to make the input data part of the stream right away by using Observable.just() to take the input data and make it into an Observable and then let the rest of the downstream operations receive it as an input. I assume that if I now cache the observable and resubscribe later that the input data is now embedded in my observable and can be accessed in any of the operators later.

Have I solved my problem in my proposed solution in a "normal" RxJava / functional way? Are there better ways to approach this problem?

1
Yeah- not using observables. How is this simpler than using a Thread or AsyncTask and passing it parameters? This code is absolutely unreadable. It seems like you're using observables just because they're the cool new thing, rather than because they actually solve a problem for you.Gabe Sechan
@GabeSechan This is a simplified scenario to illustrate the conceptual issues I am having. In my real life scenario the login part is just the beginning. I then need to make 3 more API calls to fetch more data, filter it and transform it which is a great use case for Rxjava.neonDion
That might make sense for the fetched data, but it makes none for the login call itself (I have doubts about the fetched data as well- I find that 99% of RxJava usage makes code less readable and maintainable, but I'll give it the benefit of the doubt having not seen it). But even if so there's no reason to use it for the login where it isn't making things clearer. I'd reject any code review with anything remotely like this in it.Gabe Sechan

1 Answers

1
votes

The username/password/save-flag are passed in to execute1() as parameters, marked as final. Then, in your anonymous nested classes, you make explicit references to those values, "closing over" them. The resulting observable chain has everything bound to it that it needs in order to operate.

Subscribing to the observable again will use the original username/password/save-flag.