RxJava and Immutable DiffCallback in Android

Quan Vu
AndroidPub
Published in
5 min readMay 14, 2017

In Android, DiffUtil has made us a simple life to interact with RecyclerView. Using it, you don’t have to call different methods of Adapter to update the view. If you don’t know about it, you can read it here.

Today, I gonna write one of the way using RxJava to transform the data from the data source into view data and finally turn into DiffResult. The goal also are making DiffCallback immutable and putting DiffResult calculation into background. The result will later be used to update the adapter. TL,DR? Here is the source code.

1. The data transformation flow

Data transformation flow
  • Model is the data that comes from cloud, local or memory data
  • View State (or View Model) is the data transformed from the model. The View State represents data that will be later displayed to the view
  • DiffResult is the result of DiffUtil.calculateDiff, we will then use this result to update the view

2. Components

  • Repository will encapsulate the data source. It will be a reactive source. Whenever the data has been updated, it will emit new data
  • Presenter will take the data emitted by Repository and then calculate the DiffResult
  • View is passive view, it’s mission is to receive the result and update the view

3. Implementation

Repository

public class TodoRepo {
private BehaviorSubject<List<Todo>> todos;

public TodoRepo() {
todos = BehaviorSubject.createDefault(new ArrayList<>());
}

public Observable<List<Todo>> getTodos() {
return todos.hide();
}
}

The Repository will hold a todos subject. Whenever there is a operation that updates the datasource, this subject will also be notified. By doing this, any subscription that subscribes to the getTodos() observable will be notified when data change. Next I will show the operations.

public class TodoRepo {
private BehaviorSubject<List<Todo>> todos;

public TodoRepo() {
todos = BehaviorSubject.createDefault(new ArrayList<>());
}

public Observable<List<Todo>> getTodos() {
return todos.hide();
}

private List<Todo> currentTodos() {
return todos.getValue();
}

public Completable delete(Todo todo) {
return Completable.fromAction(() -> {
List<Todo> newTodos = new ArrayList<>(currentTodos());
newTodos.remove(todo);
todos.onNext(newTodos);
});
}

public Completable create(String title, String dueDate) {
return Completable.fromAction(() -> {
List<Todo> newTodos = new ArrayList<>(currentTodos());
newTodos.add(0, new Todo(nextId(), title, dueDate, false));
todos.onNext(newTodos);
});
}
}

These operations are written using Completable. So, what is a Completable? In RxJava2, a Completable is a source that only emits complete event or error event.

In our case, we don’t need new data return after create or delete operation. The reason why is we subscribe and listen to data — the truth — through only one way using getTodos(). The repository now is a hot stream that provide reactive data

Transform data to ViewModel

class TodoListPresenter {    void onCreate() {
todoListScreen.initialize();
disposables.add(todoRepo.getTodos()
.map(this::toViewModels)
...
}

private List<TodoViewModel> toViewModels(List<Todo> todos) {
return Observable.fromIterable(todos)
.map(TodoViewModel::new)
.toList()
.blockingGet();
}
void create(String title, String dueDate) {
disposables.add(todoRepo.create(title, dueDate)
.subscribe(todoListScreen::scrollToTop));
}
...
}

In the Presenter, we can start transform data to view state. RxJava provides an awesome way to transform a list of data. Using this, we don’t have to iterate through the list ourself.

Any operation that used to update data like create operation, we don’t have to deal with the result. Again, new data will always be provided through todoRepo.getTodos()

Immutable DiffCallback

DiffCallback is a bridge that will be used to calculate the DiffResult. We have already known that to create a DiffCallback, we need to provide two data (the oldDataSet and newDataSet)

At this step, we need to somehow get the oldDataSet to combine with newDataSet emitted by the repository. But we also want to make DiffCallback immutable (it means we will recreate DiffCallback every-time we need one)

There many way to we can keep track of last data. One of the approach is using scan of RxJava, you can find it here. But for me as a math guy, I found that approaches using accumulation of scan is not suitable — the DiffResult is not an accumulation of dataset. All we need to do is just echo the last data

My approach is creating an EchoTransformer

public class EchoTransformer<T> implements ObservableTransformer<T, Pair<T, T>> {
private T lastValue;

public EchoTransformer(T initValue) {
lastValue = initValue;
}

@Override
public ObservableSource<Pair<T, T>> apply(Observable<T> upstream) {
return upstream.map(newValue -> {
Pair<T, T> result = new Pair<>(lastValue, newValue);
lastValue = newValue;
return result;
});
}
}

This transformer transform a reactive stream to emit a lastValue together with the newValue → create a pair of value. Now we implement DiffCallback

public class TodoDiffCallback extends DiffUtil.Callback {
private final List<TodoViewModel> oldList;
private final List<TodoViewModel> newList;

public TodoDiffCallback(Pair<List<TodoViewModel>, List<TodoViewModel>> data) {
checkNotNull(data.first);
checkNotNull(data.second);
this.oldList = data.first;
this.newList = data.second;
}

@Override
public int getOldListSize() {
return oldList.size();
}

@Override
public int getNewListSize() {
return newList.size();
}

@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { ... }

@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { ... }
}

Finally, we introduce the full transformation in the Presenter onCreate method

void onCreate() {
disposables.add(todoRepo.getTodos()
.map(this::toViewModels)
.compose(new EchoTransformer<>(Collections.emptyList()))
.map(TodoDiffCallback::new)
.map(DiffUtil::calculateDiff)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> todoListScreen::applyDiff));
}
Now we have a beautiful reactive stream!!!

The Adapter

class TodoListAdapter extends RecyclerView.Adapter<TodoListAdapter.ViewHolder> {
private final TodoListPresenter todoListPresenter;
private final LayoutInflater layoutInflater;

TodoListAdapter(Context context, TodoListPresenter todoListPresenter) {
this.todoListPresenter = todoListPresenter;
this.layoutInflater = LayoutInflater.from(context);
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(layoutInflater.inflate(R.layout.item_todo, parent, false));
}

private List<TodoViewModel> getTodos() {
return todoListPresenter.getTodos();
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.bind(getTodos().get(position));
}

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

We all know that the adapter needs reference to the data source. We also want to keep as less state as possible to prevent side effects. My approach here is to let adapter references to the view state in the presenter. Now we need to introduce that state

class TodoListPresenter {
private List<TodoViewModel> todos = Collection.emptyList();
List<TodoViewModel> getTodos() {
return todos;
}
}

We also need to update our stream to collect the new data and update above state. From my experience we should collect the data in the main thread after finish all heavy calculation. By doing this, we could stay away from race-condition when the adapter trying to update the view

class TodoListPresenter {
private final TodoListScreen todoListScreen;
private final TodoRepo todoRepo;

private List<TodoViewModel> todos = Collection.emptyList();

List<TodoViewModel> getTodos() {
return todos;
}

void onCreate() {
todoListScreen.initialize();
disposables.add(todoRepo.getTodos()
.map(this::toViewModels)
.compose(new EchoTransformer<>(todos))
.map(TodoDiffCallback::new)
.map(callback -> new Pair<>(DiffUtil.calculateDiff(callback), callback.newList))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
todos = result.second;
todoListScreen.applyDiff(result.first);
}));
}

4. Conclusion

Applying RxJava to calculate DiffUtil help use solve below problems

  1. Making the data flow become simple
  2. Making the DiffCallback immutable
  3. Putting the calculation of DiffCallback into a background
  4. Keeping less state to reduce side effect

The above code is still not testable because the Presenter still touching Android. Because testing out of the scope of the article, I would love to put it in the source code rather then here.

Thank you for reading and happy coding!!!

--

--