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!

 

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