Collection Binding with DiffUtil and MVVM in Android

Datetime:2017-03-27 05:14:33         Topic: MVVM Model  Andrew Development          Share        Original >>
Here to See The Original Article!!!

The list user interface pattern has gained it popularity in both desktop and mobile. Today, I will show you one of my approve to apply binding a list of data from ViewModel into the View.

Android DataBinding will also be applied in the tutorial. Don’t know what is Android DataBinding? You can read it here .

I also apply DiffUtil — a high performance mechanism to notify the list about new data. If you do not know what is DiffUtil you can read about it here .

What is MVVM?

source: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel
  1. The ViewModel contains observable properties and commands. It has no knowledge about the View. Because of this, the ViewModel can standalone, we can write the ViewModel and its tests before touching the View layer.
  2. The View layer contains pure UI components. It only references the ViewModel (Presentation Logic Layer), not the Model itself. The View will bind to ViewModel’s commands and properties. Whenever these properties change, the View will be notified and updated properly.
  3. The Model is where the business logic resides. It also has no knowledge about the ViewModel, but rather sends out observable notifications when updated.

Application Mockup

Let’s look at an example of this with a demo application. The sample application is just a simple todo list. The screen will have following mockup:

Todo List Application Mockup

Some requirements of the applications:

  • User can click New button to create a new todo
  • For each todo, the user can mark it as completed and vice versa
  • Marking a todo as completed make the Remove button show up
  • User can delete a completed todo

Applying MVVM

1. View

TodoListActivitywill act like the View. It’s job is to do binding logic into the ViewModel

2. Model

The model layer includes TodoRepo and Todo

  • TodoRepo is acting like a data access layer to manipulate the data (for the scope of the article all data will be in memory):
public interface TodoRepo {
List<Todo> getTodos();
void updateTodo(Todo todo);
void deleteTodo(Todo todo);
Todo createTodo(String title, String dueDate);
}
  • Todo is acting like an entity. It reflects the data of the application:
public class Todo {
public final long id;
public final String title;
public final String dueDate;
public final boolean completed;

public Todo(long id, String title, String dueDate, boolean completed) {
this.id = id;
this.title = title;
this.dueDate = dueDate;
this.completed = completed;
}
}

3. The ViewModel

The ViewModel layer includes TodoListViewModel and TodoViewModel:

  • TodoListViewModel includes commands and properties of TodoListActivity.
  • TodoViewModel reflects the state of an item in the list of the View
public class TodoViewModel {
public final long id;
public final String title;
public final String dueDate;
public final boolean completed;

public TodoViewModel(Todo todo) {
this.id = todo.id;
this.title = todo.title;
this.dueDate = todo.dueDate;
this.completed = todo.completed;
}

private TodoViewModel(TodoViewModel todo, boolean completed) {
this.id = todo.id;
this.title = todo.title;
this.dueDate = todo.dueDate;
this.completed = completed;
}

public TodoViewModel setCompleted(boolean completed) {
return new TodoViewModel(this, completed);
}

public int removeVisibility() {
return completed ? View.VISIBLE : View.INVISIBLE;
}

public Todo toModel() {
return new Todo(id, title, dueDate, completed);
}
}

So what is the difference between TodoViewModel and Todo . For example, the Todo does not care about “The remove button is shown or not” but TodoViewModel has to describe this information

If you notice the function setCompleted, It created a new instance of ViewModel instead of modify the variable completed . This is an example of Immutable object. You definitely have seen this kind of programming before in class String of Java. Every time you try to modify an existing using substring , instead of modify the value, it creates a new instance of String. You can read more about Immutable here . Later I will show you the reason why I am using Immutable objects.

ListBinder and DiffCallBack implementation

1. DiffCallBack

The DiffCallBack class is just an abstract generic class that extends DiffUtil.Callback of Android

public abstract class DiffCallBack<E> extends DiffUtil.Callback {
protected List<E> oldList = new ArrayList<>();
protected List<E> newList = new ArrayList<>();

void setData(List<E> oldList, List<E> newList) {
this.oldList = oldList;
this.newList = newList;
}

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

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

2. TodoDiffCallBack

Let’s implement the real DiffCallBack of the application

public class TodoDiffCallback extends DiffCallBack<TodoViewModel> {

@Inject
public TodoDiffCallback() {
}

@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldList.get(oldItemPosition).id == newList.get(newItemPosition).id;
}

@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return oldList.get(oldItemPosition).title.equals(newList.get(newItemPosition).title)
&& oldList.get(oldItemPosition).dueDate.equals(newList.get(newItemPosition).dueDate)
&& oldList.get(oldItemPosition).completed == newList.get(newItemPosition).completed;
}
}

3. ListBinder

The ListBinder is the most important class of the article:

  • It depends on the DiffCallBack
  • It holds the reference to current list of data (the view-models)
  • It receives a new list of data and calculate the DiffResult
  • It notifies the listeners (View) about data changes
public class ListBinder<E> {
private final DiffCallBack<E> diffCallBack;
private List<E> current = new ArrayList<>();
private OnDataChangeListener onDataChangeListener;
public ListBinder(DiffCallBack<E> diffCallBack) {
this.diffCallBack = diffCallBack;
}
interface OnDataChangeListener {
        void onChange(DiffUtil.DiffResult diffResult);
    }
void setOnDataChangeListener(OnDataChangeListener onDataChangeListener) {
this.onDataChangeListener = onDataChangeListener;
}
public void notifyDataChange(List<E> data) {
verifyMainThread();
DiffUtil.DiffResult diffResult = calculateDiff(data);
if (onDataChangeListener != null) {
onDataChangeListener.onChange(diffResult);
}
}
private DiffUtil.DiffResult calculateDiff(List<E> data) {
List<E> newList = new ArrayList<>(data);
diffCallBack.setData(current, newList);
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallBack);
current = newList;
return diffResult;
}
}

There are two notices here:

  • I create a new ArrayList when calculate the diff:
List<E> newList = new ArrayList<>(data);

Doing this to make sure that the current and new list are two different lists with different references

  • Remember the Immutable TodoViewModel? What happen if it were Mutable?

The result is: the current and new list will hold the same references to the view-models. The two lists are two different objects but all of the items are the same. This will leak to the wrong result of

DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallBack);

TodoListViewModel

To make it simple, I will implement only two functions “Mark a todo as completed” and “Get all todo”

public class TodoListViewModel {
private final ListBinder<TodoViewModel> todoListBinder;
private final TodoRepo todoRepo;
private final List<TodoViewModel> todos = new ArrayList<>();
private PublishSubject<Integer> scrollTo = PublishSubject.create();

TodoListViewModel(ListBinder<TodoViewModel> todoListBinder, TodoRepo todoRepo) {
this.todoListBinder = todoListBinder;
this.todoRepo = todoRepo;
}

public ListBinder<TodoViewModel> getTodoListBinder() {
return todoListBinder;
}

List<TodoViewModel> getTodos() {
return todos;
}
void initialize() {
        todos.addAll(toViewModels(todoRepo.getTodos()));
        todoListBinder.notifyDataChange(todos);
    }
void setCompleted(int position, boolean completed) {
TodoViewModel viewModel = todos.get(position);
if (viewModel.completed != completed) {
viewModel = viewModel.setCompleted(completed);
todoRepo.updateTodo(viewModel.toModel());
todos.set(position, viewModel);
todoListBinder.notifyDataChange(todos);
}
}
}

Layout and Binding

I am not gonna go into detail about Android DataBinding because it’s not the main topic of the article. If you don’t like Android DataBinding. You can also write binding logic in TodoListActivity

  • BindingUtils.java
public class BindingUtils {

@BindingAdapter("listBinder")
public static <E> void bindItems(RecyclerView recyclerView, ListBinder<E> listBinder) {
RecyclerView.Adapter adapter = recyclerView.getAdapter();
if (adapter != null) {
listBinder.setOnDataChangeListener(diffResult ->
diffResult.dispatchUpdatesTo(adapter));
}
}
}
  • activity_todo_list.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="com.apidez.bindingcollection.ui.TodoListViewModel" />
<variable name="viewModel" type="TodoListViewModel" />
</data>

<android.support.v7.widget.RecyclerView
android:id="@+id/rvTodos"
app:listBinder="@{viewModel.todoListBinder}"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</layout>

Unit testing

At this moment, you may be wondering how to write unit tests for the ViewModel

  • Set up the test
public class TodoListViewModelTest {
private TodoListViewModel todoListViewModel;
private TestObserver<Integer> testScrollTo = TestObserver.create();

@Mock
ListBinder<TodoViewModel> listBinder;
@Mock
TodoRepo todoRepo;

@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
todoListViewModel = new TodoListViewModel(listBinder, todoRepo);
when(todoRepo.getTodos()).thenReturn(mockTodos());
}
private List<Todo> mockTodos() {
List<Todo> todos = new ArrayList<>();
todos.add(new Todo(1, "title 1", "date 1", true));
todos.add(new Todo(2, "title 2", "date 2", false));
todos.add(new Todo(3, "title 3", "date 3", false));
return todos;
}
}
  • Test the “Get all todo” function
@Test
public void testInitialize() throws Exception {
    todoListViewModel.initialize();
    assertEquals(1, todoListViewModel.getTodos().get(0).id);
    assertEquals(2, todoListViewModel.getTodos().get(1).id);
    assertEquals(3, todoListViewModel.getTodos().get(2).id);
      verify(listBinder).notifyDataChange(todoListViewModel.getTodos());
}
  • Test the “Mark todo as completed” function
@Test
public void testSetCompletedViewModel() throws Exception {
    todoListViewModel.initialize();
    todoListViewModel.setCompleted(1, true);
    assertTrue(todoListViewModel.getTodos().get(1).completed);
    assertEquals(2, todoListViewModel.getTodos().get(1).id);
    verify(listBinder, times(2)).notifyDataChange(todoListViewModel.getTodos());
}
  • You can find the full test suit here .

Conclusion

This article is my approach to implement binding collection in Android with MVVM. If you have other ways to do it, I would love to know and do some discussion. Overall, what I aim for is an elegant, clear and testable solution for developing Android with MVVM.

You can find the full source code of the sample here . Thank you for reading and happy coding.








New