RX by Example

This is the first in a series of blog posts co-authored with our friends over on the Nordstrom Technology team. This one might be a bit technical for the non-developer crowd. Today we’ll be taking a look at Nordstrom’s use of Reactive Java and Reactive Android.

We’d be lying to say that our adoption of RX-Android  hasn’t been a somewhat bumpy ride. We’ll get into the challenges reactive brought in a later post. Today we’re going to  take a look at some of the simplifications reactive has brought to the code base.

For those completely new to the reactive model see

http://paulstovell.com/blog/reactive-programming

https://medium.com/reactive-programming/what-is-reactive-programming-bc9fa7f4a7fc

When building any application these days you’ll need to make a large number of network calls. These calls are often, but not always dependent on each other. Additionally they need to update the state of application UI, often in many different places. This leads to several common problems.

The first problem is in a series of network calls dependent on each other. In this example we need to authenticate the user, fetch their shopping cart, and some details about one of the items in the shopping cart.

The traditional approach looks something like this.

mAuthentication.Authenticate(username, password, new Callback<AuthenticationResult>() {
  @Override
  public void success(AuthenticationResult authenticationResult, Response response) {
    mShoppingCartService.shoppingCarFor(authenticationResult, new Callback<ShoppingCart>() {
      @Override
      public void success(ShoppingCart shoppingCart, Response response) {
        mProductDetails.getItemDetails(shoppingCart.mainItem, new Callback<itemDetail>() {
          @Override
          public void success(ItemDetail itemDetail, Response response) {
            updateUIWith(itemDetail);
          }
          @Override
          public void failure(RetrofitError error) {
            //something with Error
          }
      }
      @Override
      public void failure(RetrofitError error) {
        //something with Error
      }
    });
  }
  @Override
  public void failure(RetrofitError error) {
    //something with Error
  }
});

Welcome to nested callback hell. Sure it works but it’s a pain to read and thus pretty simple to introduce bugs. A common alternative is to use class variables to store intermediate state. This opens us up to inconsistent state and mutability issues.

Here it is with Reactive:

Subscription shoppingCartSubscription = mAuthentication.authenticate(username, password).flatMap(new Func1<AuthenticationResult, Observable<ShoppingCart>>() {
  @Override
  public Observable<ShoppingCart> call(AuthenticationResult authenticationResult) {
    //This returns an Observable that will emit the Shopping cart for the authenticationResult
    return mShoppingCartService.shoppingCartFor(authenticationResult);
  }
}).flatMap(new Func1<ShoppingCart, Observable<ItemDetail>>() {
  @Override
  public Observable<ItemDetail> call(ShoppingCart shoppingCart) {
    //This returns an Observable that will emit the ItemDetail for the mainItem in the ShoppingCart
    return mProductDetails.itemDetailsFor(shoppingCart.mainItem);
  }
//We now can do whatever we need with the Item Details. No need to store all the previous responces
}).subscribe(new Observer<ItemDetail>() {
  @Override
  public void onCompleted() {
  }
  @Override
  public void onError(Throwable e) {
  }
  @Override
  public void onNext(ItemDetail itemDetail) {
    updateUIWith(itemDetail);
  }
});

Composition is simple and straightforward.  There’s very little difference in handling a network async call to how you would just handle a list of items in an array.

You also get:

  • filters: ignore unwanted return values
  • throttles: prevent excessive network calls
  • maps: transform the data to something different
  • etc.

The second problem arises when you have several independent network calls to combine into a single result. We could use the previous pattern but then our result would take the sum of the call length rather than the length of the longest call. Doing this without RX total pain. Make all the calls. Keep track of when they’re all done in a class variable. When a callback fires check if we are all done. If so run the final callback.

Something like this:

mUser.PersonalData(user, new Callback<PersonalData>() {
  @Override
  public void success(PersonalData personalData, Response response) {
    mPersonalData = personalData;
    done1 = true;
    if (done2 && done3) {
      updateUIwith(mPersonalData, mShippingInfo, mCreditCard);
    }
  }
  @Override
  public void failure(RetrofitError error) {
   //something with Error
  }
});
mUser.shippingInfo(user, new Callback<ShippingInfo>() {
  @Override
  public void success(ShippingInfo shippingInfo, Response response) {
    mShippingInfo = shippingInfo;
    done3 = true;
    if (done2 && done1) {
      updateUIwith(mPersonalData, mShippingInfo, mCreditCard);
    }
  }
  @Override
  public void failure(RetrofitError error) {
    //something with Error
  }
});

mUser.CreditCard(user, new Callback<CreditCard>() {
  @Override
  public void success(CreditCard creditCard, Response response) {
    mCreditCard = creditCard;
    done2 = true;
    if (done1 && done3) {
      updateUIwith(mPersonalData, mShippingInfo, mCreditCard);
    }
  }
  @Override
  public void failure(RetrofitError error) {
    //something with Error
  }
});

This works OK, but now you’ve got a bunch of different incomplete states your object can be in. It’s not so bad now but with a few more calls it can get difficult to see interdependent calls. Worse yet adding a new call into the set requires making changes all over. You can avoid some of this by being clever but let’s take a look at the simple approach instead.


With Reactive:
Subscription userInfoSub = Observable.combineLatest(mUser.personalData,mUser.shippingInfo,mUser.defaultCreditCardInfo
 new Func3<PersonalData, ShippingInfo, CreditCard, UserDataViewModel>() {
  @Override
  public UserDataViewModel call(PersonalData personalData, ShippingInfo shippingInfo, CreditCard creditCard) {
    //UserDataViewModel can do whatever logic it needs to merge all three responses.
    return UserDataViewModel.create(personalData, shippingInfo, creditCard);
  }
}).subscribe(new Observer<UserDataViewModel>() {
  @Override
    public void onCompleted() {
  }
  @Override
    public void onError(Throwable e) {
  }
  @Override
  public void onNext(UserDateViewModel userDataViewModel) {
    updateUIwith(userDataViewModel);
  }
});

In the RX example you see that nothing outside this call is mutable. You have no risk of someone later inadvertently modifying your data or of the data being in inconsistent states. There’s no need to reset everything before making the call once more.  Testing the behavior becomes drastically simpler.  You see in one place the flow of logic which is taking place. It is a bit of a paradigm shift. However, it’s going to be hard to go back after looking behind the curtain.

Some gotchas:

Subscriptions: Subscribe calls return Subscription objects. Once you unsubscribe the Observer will no longer receive events. Call unsubscribe on destruction lifecycle events in Android.

Observer – Observers receive the final event. In general you want to use them to update the UI. You may want to .observeOn(AndroidSchedulers.maintThread()) to make sure the update is on the correct thread.

 

Thanks for reading. If you have any questions or would like to know more about working with Reactive Java at Nordstrom shoot us an email.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s