RX-Java, Spock, Groovy, Android-bindings… Android might now be testable! say what?

Along with our friends at Nordstrom we have some fundamental values we believe in. These include paired style of approaches to development, TDD/BDD, etc. Some of which up until recently have been a bit tough to get in on with Android. Now I’m not going argue the merits of TDD/BDD, you can look that up. I’ll go through some of what we’ve been looking at to help testability and simplicity in Android.

As most of you know testing in Android has been a pretty hostile Environment.  The next three posts will cover the following.

  1. Setting up Android-bindings – (This post ;))
  2. Using RX for the events from the UI and the rest of the system.
  3. Using Spock and Groovy to test our POJOs.

If you want to check out an app that is experimenting with some of these designs check out https://play.google.com/store/apps/details?id=com.liffft.iwanna (it is just a sample and not our “large scale implementations”  :))

Now lets gets started.

  1. DATA-BINDING

Per https://developer.android.com/tools/data-binding/guide.html data-binding is still in beta. However, with the rc1 release and my experience with it thus far it’s production ready.  I mean still test your app but it’s pretty solid at this point.  There is still a lag in the documentation so I’ll give you the current configuration I’m using

You’ll need to make the following updates to gradle.

buildscript.dependencies add

classpath “com.android.tools.build:gradle:2.0.0-aplha5

classpath “com.android.databinding:dataBinder:1.0-rc4

apply the plugins

 apply plugin: ‘com.android.application’

 apply plugin: ‘com.android.databinding

— you’ll also need to add

android { dataBinding { enabled = true }}

Now we wrap our normal xml layouts with a new <layout> tag.  Inside of this define <data> tags. Here’s the example of what my layout looks like.

<layout xmlns:android="http://schemas.android.com/apk/res/android">
  <data>
    <variable name="createEventViewModel" type="com.liffft.iwanna.activities.create.CreateEventViewModel"/>
  </data>
  <LinearLayout...
    //At some point in time a text view has
    <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/event_name_edit_text" android:layout_weight="1" android:text="@{createEventViewModel.eventName}" android:hint="Play Soccer" />
    //And many more based on the properties of the viewModel
  </LinearLayout>
</Layout>

Now the CreateEventViewModel extends BaseObservable and has @Bindable annotations for the getters for properties like

@Bindable public String getEventName() { return eventName; }
//and the setter looks like
public void setEventName(String eventName) {
  this.eventName = eventName;
  this.notifyPropertyChanged(BR.eventName);
}

Notice the notifyPropertyChanged. This is what notifies the UI to update from the model. This doesn’t need to be in the setter of the property. It can be anywhere where the side effect to the value takes place. In the Activity we can use

  CreateEventViewBinding createEventViewBinding = DataBindingUtil.setContentView(this, R.layout.create_event_view);
  createEventViewBinding.setCreateEventViewModel(mCreateEventViewModel);

CreateEventViewBinding is auto generated for us from the R.layout.create_event_view file name. It cases it and adds binding. At this point we’re ready to go. We have a great starting point to move much of the business objectives in the ViewModel. Notice there shouldn’t be any android stuff in the viewModel. This makes a nice and easy POJO to test with Spock and no need for the emulator.

Now for a few more advanced tips we’ll cover the following.

  • Fragments
  • The new NavigationView from the Design lib
  • RecyclerView
  • custom bindings

 

  1. Fragments – This is actually pretty simple.  The following code snippet should give you an example of the onCreateView and onViewCreated
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
  return inflater.inflate(R.layout.create_event_view, container, false);
}

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
  super.onViewCreated(view, savedInstanceState);
  CreateEventViewBinding createEventViewBinding = DataBindingUtil.bind(view);
  createEventViewBinding.setCreateEventViewModel(mCreateEventViewModel);
}

Notice I’m injecting the ViewModel so you’ll need to make sure to instantiate it somewhere.

  1. NavigationView – Unfortunately we can only bind the drawer header .  The list of items is actually a menu which I don’t believe can be used with bindings yet. If someone knows better let me know. – Again a code snippet from my Activity that is using the NavigationDawer

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  DrawerHeaderBinding drawerHeaderBinding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.drawer_header, mNavigationView, true);
  drawerHeaderBinding.setHeader(mHeaderViewModel);
}

I think it’s pretty evident but make sure to include the correct layout for the header and give it the parent mNvaigationView.  This will now bind anything you want in the header to the mHeaderViewModel.

  1. RecyclerView – This is the one that’s a bit more tricky. I’ll post the code sample and then speak to it.

public class EventListAdapter extends RecyclerView.Adapter<EventListAdapter.BindingHolder>{

private ArrayList<EventInterface> events;
private ProjectRouter mProjectRouter;
@Inject
public EventListAdapter(ProjectRouter projectRouter){
  mProjectRouter = projectRouter;
  events = new ArrayList<>();
}

public void addAll(List<EventInterface> eventInterfaces){
  events.clear();
  events.addAll(eventInterfaces);
  notifyDataSetChanged();
}

@Override
public BindingHolder onCreateViewHolder(ViewGroup parent, int viewType) {
  ViewDataBinding dataBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.single_item, parent, false);
  return new BindingHolder((SingleItemBinding)dataBinding);
}

@Override
public void onBindViewHolder(BindingHolder holder, int position) {
  final EventViewModel eventViewModel = new EventViewModel(events.get(position),mProjectRouter);
  SingleItemBinding itemBinding = (SingleItemBinding)holder.mViewDataBinding;
  itemBinding.setEvent(eventViewModel);
}

@Override
public int getItemCount() {
  return events.size();
}

@Override
public void onViewRecycled(BindingHolder holder) {
  holder.mViewDataBinding.getEvent().unsubscribe();
  super.onViewRecycled(holder);
}

public static class BindingHolder extends RecyclerView.ViewHolder {
  // each data item is just a string in this case
  public SingleItemBinding mViewDataBinding;
  public BindingHolder(SingleItemBinding viewDataBinding) {
    super(viewDataBinding.getRoot());
    mViewDataBinding = viewDataBinding;
  }
}

}

I’m using Roboguice to inject project dependencies right now so ignore that. But, what you need to pay attention to is that our Bindingholder is going to store our viewBinding. The terms here get a little overloaded (sorry!). Don’t confuse the onBindViewHolder with Data Binding.  It’s for the RecyclerView and is where we grab the DataBinding class and set the datamodel. The CreateViewHolder is where we actually create the binding and add it to the holder.

Note for those checking out the onViewRecycled, this is a precursor to some RX binding goodness 🙂

  1. Custom Bindings – so this is something I like. I have more examples coming up in the RX post however here is a quick and dirty example that can get you started

@BindingAdapter("bind:picassoImageUrl")
public static void loadImage(ImageView imageView, String url) {
  if (url !=null && !url.isEmpty()) {
    Picasso.with(imageView.getContext()).load(url).resize(400, 400).into(imageView);
  }
}
@BindingAdapter("bind:imageThumbnailLocalURLString")
public static void loadImageThumbnail(ImageView imageView, String imagePath){
  if(imagePath != null && !imagePath.isEmpty()) {
    Bitmap thumbImage = ThumbnailUtils.extractThumbnail(BitmapFactory.decodeFile(imagePath), 400, 400);
    imageView.setImageBitmap(thumbImage);
  }
}

in the layout use something like

  <ImageView….. app:picassoImageUri="@{settingsViewModel.imageURL}"; />

Here we can now set a model String object to the picassoImageUri. Whenever it’s updated it will go and load that image url to the ImageView. The imageThumbnailLocalURLString is an other example for a local file. You should be getting the idea!

Thanks for reading. If you have any questions or would like to know more about working with us at LIFFFT and the Nordstrom Technology team feel free to shoot us an email!

 

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.