RxJava - Handling Configuration Changes With Request Observables

Edit (10/02/2016): My most recent thoughts on how Observables should be handled can be found here.

I'm a big fan of RxJava, I have been using it for almost a year now, and I encourage it's use in Android development. If you haven't read it yet, Daniel Lew's series on RxJava "Grokking RxJava" is a great introduction to RxJava and it covers many of the reasons why you might want to use it in your applications. The "Lifecycle" section of Part 4 of his series is a good introduction for this article, so I recommend you read it before continuing.


The goal of this article is to start a discussion around the best practices for continuing a subscription after a configuration change (e.g. the user rotates the screen). That is, if we make a request and a configuration changes on the device, we can re-subscribe our new UI to the same observable and not make 2 network requests (less requests makes Reto Meier proud).


In Dan's article, he discusses how we can use RxJava's cache (or replay) operators to prevent a new network call being created every time the UI subscribes to a given observable. The only thing left to discuss in this area is where we store the observable while a configuration change is happening:

Where you store request I leave up to you, but like all lifecycle solutions, it must be stored somewhere outside the lifecycle (a retained fragment, a singleton, etc).

This quote can be found in the aforementioned Lifecycle section. This article is going to explore many of the different ways we can store request Observables outside of the UI lifecycles so that we can handle configuration changes correctly, and discuss which ones I feel are best and why.


The first thing I thought of was to simply have some singleton (or retain fragment) cache that I put all my Observables in, and remove them when I'm done with them. The pattern would look like this:

  • UI creates observable
  • UI stores observable in cache
  • UI subscribes to observable

e.g.:

observable = // <create observable here>

// Cache the observable 
observableCache.setObservable(observable);

// Subscribe
subscription = observable.subscribe(...);

If a configuration change occurs:

  • Old UI unsubscribes from observable
  • New UI gets observable from cache
  • New UI subscribes to observable

e.g.:

@Override public void onDestroyView() {
    super.onDestroyView();
    subscription.unsubscribe();
}

@Override 
public void onViewCreated(View view, 
             @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    // Get observable out of the cache
    Observable<String> observable = 
             observableCache.getObservable();

    // If it exists, subscribe to it
    if (observable != null) {
        subscription = observable.subscribe(onNext);
    }
}

And then, after the UI consumes the result, the UI then removes the observable from the cache.

@Override public void onNext(String s) {
     // <do something with result>

     // Clear the cache
     observableCache.setObservable(null);
}

Side note: while this observable cache could be a retain fragment, I would recommend that you manage your cache using a DI library like Dagger 1 or 2. Retained Fragments have several problems.


So, what is the problem with this approach?

To me, it seems like a lot of code to have to write just to use Observables from within your views. Not only do you have to create the cache(s), your views now have to remember to do all of the steps above. For these reasons, I haven't really seen this approach used too often.


I started trying to look around for what other people are doing to solve this problem, and for the most part, people aren't. Particularly in hello world examples. As a result, their code looks really clean and minimal, but their code is technically full of bugs. So if you're reading this article and you have open source code that doesn't handle configuration changes, I urge you to update your code and perhaps we can come up with something even better than the approaches described in this article.

I did find some examples of code bases that have considered this problem, and solved it using different approaches. One interesting example is to use an event bus as well as RxJava. The pattern would look like this:

  • UI subscribes to the event bus
  • UI posts an event to the bus to start a request
  • Observable is subscribed to outside of the UI
  • Events are posted after observable completes
  • UI handles events

e.g.:

UI:

bus.register(this);
bus.post(new SomeRequestEvent());

@Subscribe public void onSomeNext(SomeNext e) {
    // <handle onNext>
}

@Subscribe public void onSomeError(SomeError e) {
    // <handle onError>
}

@Subscribe public void onSomeComplete(SomeComplete e) {
    // <handle onComplete>
}

Non UI:

@Subscribe public void onSomeRequestEvent(SomeRequestEvent e) {
     Observable<SomeResult> observable = <create observable>
     observable.subscribe(new Observer<SomeResult>() {
             @Override public void onNext(SomeResult r) {
                  bus.post(new SomeNext(r));
             }
             @Override public void onError(Throwable t) {
                  bus.post(new SomeError(t));
             }
             @Override public void onComplete() {
                  bus.post(new SomeComplete());
             }
     }
}

If a configuration change occurs;

  • The old UI unsubscribes from the bus
  • The new UI subscribes to the bus

e.g.:

@Override 
public void onViewCreated(View view, 
                 @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    bus.register(this);
}

@Override public void onDestroyView() {
    super.onDestroyView();
    bus.unregister(this);
}

Is there problems with this approach? There are a few that I can see.

One potential problem might be that your UI won't receive updates if it is currently unsubscribed. For example, imagine pressing the home key in low memory conditions. The UI is unsubscribed as the activity is destroyed. If the UI is gone, who is left to consume the responses? And yes, you can have sticky events, but sticky events can't be used in every situation.

Another problem might be that if you have multiple Fragments that are subscribed to the same event type, but you only want to update the UI on one of those fragments after a network request completes, then the shared event bus starts to look like a problem. An example of this in action might be if you have a fragment view pager, and you load some data in each fragment, you don't want all of the other fragments receiving your events.

Finally, you're going to end up with so many event objects as your application grows.


So what else could work?

One thing that looks really nice to me is when you combine RxJava with MVP when the Presenters themselves survive configuration changes:

public class SomePresenter {
    SomeView view;
    SomeViewModel missedResult;

    public void bindView(SomeView view) {
        this.view = view;
        if (viewModel != null) {
            view.setViewModel(missedResult);
            missedResult = null;
        }
    }

    public void unbindView() {
        this.view = null;
    }

    public void loadSomething() {
        view.showLoading();
        Observable<SomeResult> observable = <create observable>
        observable.map(new SomeViewModelTransform())
                  .subscribe(observer);
    }

    private Observer<SomeViewModel> observer =
                new Observer<SomeViewModel>() {
        @Override public void onNext(SomeViewModel r) {
            if (view != null) {
                view.setViewModel(r);
            } else {
                missedResult = r;
            }
        }
        ...
    }
}

Now all the views are responsible for is binding/unbinding to/from the presenter, and initiating the load action:

@Override 
public void onViewCreated(View view, 
                 @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    presenter.bindView(this);
}

@Override public void onDestroyView() {
    super.onDestroyView();
    presenter.unbindView();
}

@OnClick(R.id.button) public void buttonClicked() {
    presenter.loadSomething();
}

This looks good.

The big question left though is, how do you control the scoping of the Presenters? That is, how do you make sure they are only destroyed when the Fragment is removed? My next article will be on MVP and will cover this exact topic!

Feedback is welcome, especially if you know of open source repositories that handle this problem in an elegant manner.

Brad Campbell

Android application developer, currently working as the lead on the ANZ goMoney NZ applications. All of the opinions and code on this blog are my own, and not that of ANZ.