How to efficiently call API and update each item dynamically on a RecyclerView in Android

We would have used libraries like Picasso to download and display images for items on a RecyclerView in Android. How about doing the same for retrieving some text data from an API for each item in a RecyclerView and displaying it dynamically and efficiently?

Let us consider a real world scenario where an RecyclerView displaying different set of data from different source for each item. Now based on the visibility of the item, we need to fetch some real time data from a remote server specifically for that item.

Below I have outlined a possible way to do the above efficiently using Rx, Retrofit and RecyclerView. I will skip the boiler plate code required for RecyclerView, depicting only the required logic to achieve out goal.

Now this is how our Adapter and ViewHolder would look like.

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyHolder> {

//items to be displayed on our RecyclerView
private List<MyObject> myObjects;
//Map data structure to store the responses for dynamically called API
private Map<MyObject, Boolean> myObjectsMap = new HashMap<>();
//we need below rx.subscriptions.CompositeSubscription object to track all our subscriptions
private CompositeSubscription compositeSubscription = new CompositeSubscription();

public MyAdapter(List<MyObject> myObjects) {
this.myObjects = myObjects;
}

@Override
public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false);
return new MyHolder(view);
}

@Override
public void onBindViewHolder(MyHolder holder, int position) {
if (holder.subscription != null && holder.subscription.isUnsubscribed()) {
//this will also unsubscribe the subscription
compositeSubscription.remove(holder.subscription);
}
MyObject myObject = myObjects.get(position);
holder.bindMyObject(myObject, position);
}

@Override
public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
//unsubscribe all subscriptions
compositeSubscription.unsubscribe();
super.onDetachedFromRecyclerView(recyclerView);
}

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

public class MyHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

protected TextView myTextView;
//rx.Subscription object that we will add to our CompositeSubscription later
protected Subscription subscription;

public MyHolder(View itemView) {
super(itemView);
myTextView = itemView.findViewById(R.id.my_textview);
}

public void bindMyObject(final MyObject myObject, final Integer index) {
myTextView.setText(“”);
if (myObjectsMap.containsKey(myObject)) {
myTextView.setText(myObjectsMap.get(myObject) ? “TRUE” : “FALSE”);
} else {
getMyObjectFlag(index);
}
}

private void getMyObjectFlag(final int index) {
if (index >= myObjects.size()) {
return;
}
final MyObject myObject = myObjects.get(index);
Observable<MyObjectResponse> response = getRetrofitClient().getMyObjectResponse(myObject.id);
//MyCustomRxUtil.subscribe is my custom static method that returns an rx.Observable
//MyCustomerRxCallback is just an Interface with two methods
subscription = MyCustomRxUtil.subscribe(response, new MyCustomerRxCallback<MyObjectResponse>() {
@Override
public void onSuccess(MyObjectResponse response) {
if (response != null) {
myObjectsMap.put(myObject, true);
//we update specific item in our RecyclerView
notifyItemChanged(index);
} else {
myObjectsMap.put(myObject, false);
}
}

@Override
public void onFailure(Throwable error) {
//log error
}
});
compositeSubscription.add(subscription);
}
}
}

The logic above is to have a CompositeSubscription object at RecyclerViewAdapter level which groups all the Subscription(s) at ViewHolder level and then we manage it based on items that we are showing/reusing on RecyclerView.

We also maintain a Map object to retain the response from API, you can also cache responses using Retrofit itself.

We Subscribe to an API call whenever we bind an object, getMyObjectFlag(), to RecyclerView. And then add the Subscription to our CompositeSubscription.

When a ViewHolder is reused, Adapter calls onBindViewHolder(), where we unsubscribe.

All subscriptions are unsubscribed if the RecyclerView is detached, onDetachedFromRecyclerView(), from the parent view.

Please let me know if you need any clarifications or if you think there is a better way to do it.