LiveData с примитивами (и строками) довольно проста; Списки, с другой стороны, немного сложнее.

Я просмотрел вводное видео и рассмотрел примеры LiveData, ViewModel, LifecycleObserver, LifecycleOwner и Комната, но чувствовал, что мне нужен собственный пример, чтобы понять это. Впервые я узнал о ViewModels на примере и написал об этом еще одну статью.



В этой статье я заметил несколько проблем с примером приложения ViewModel todo (вы можете добавлять и удалять элементы todo, которые сохраняются в локальной базе данных SQLite):

  • Список задач хранится в трех местах: в представлении (завернутый в ArrayAdapter), на диске (таблица в базе данных SQLite) и в ViewModel. .
  • Более проблематично то, что нам приходилось делать два вызова из представления для каждого изменения списка задач, то есть один вызов ViewModel и один вызов ArrayAdapter (в представлении).

Думая, что решением этих проблем было использование LiveData, я сел, чтобы узнать об этом. Самый простой пример в документации довольно прост; однако он демонстрировал только обновления строки. В этом случае в представлении мы просто используем новую строку для повторной визуализации некоторого элемента представления, например, setText.

Однако в примере задачи мы обновляем List ‹User›. Сложность здесь заключается в том, что, на наш взгляд (MainActivity.java), нам нужно определить, что изменилось в списке, например, добавили ли мы что-то или удалили что-то, и выборочно обновить представление.

примечание: что интересно, в современном мире веб-разработки (с React) вам не нужно беспокоиться о выборочном обновлении представлений, поскольку React автоматически делает это для вас (вы просто повторно визуализируете весь список, и React определяет различия в представлении).

Хорошая новость заключается в том, что Google предлагает решение этой проблемы в своем BasicSample. Плохая новость в том, что этот пример слишком сложен (насколько ироничен). Отсюда и название этой статьи: Лучший базовый образец Android LiveData.

LiveData

Готовый пример доступен:



Предполагая, что вы уже знакомы с ViewModels и основами LiveData, мы рассмотрим поток данных в решении; например, добавление задачи.

В ответ на кнопку ДОБАВИТЬ в диалоговом окне Добавить новую задачу представление (MainActivity.java) вызывает addTodo в ViewModel.

MainActivity.java

...
public void onClick(DialogInterface dialog, int which) {
    String name= String.valueOf(nameEditText.getText());
    long date = (new Date()).getTime();
    mTodosViewModel.addTodo(name, date);
}
...

Важно отметить, что мы делаем только один вызов, чтобы инициировать добавление задачи; В нашем последнем примере мы были вынуждены сделать два вызова (один раз для обновления представления и один раз для обновления модели представления).

Метод addTodo ViewModel сначала сохраняет изменение (в таблице SQLite), делает глубокую копию List ‹Todo›, добавляет задачу в копию и отправляет копию в объект mTodos LiveData.

TodosViewModel.java

...
public void addTodo(String name, long date) {
    SQLiteDatabase db = mTodoDbHelper.getWritableDatabase();
    ContentValues values = new ContentValues();
    values.put(TodoContract.TodoEntry.COL_TODO_NAME, name);
    values.put(TodoContract.TodoEntry.COL_TODO_DATE, date);
    long id = db.insertWithOnConflict(TodoContract.TodoEntry.TABLE,
        null,
        values,
        SQLiteDatabase.CONFLICT_REPLACE);
    db.close();
    List<Todo> todos = mTodos.getValue();
    ArrayList<Todo> clonedTodos = new ArrayList<Todo>(todos.size());
    for(int i = 0; i < todos.size(); i++){
        clonedTodos.add(new Todo(todos.get(i)));
    }
    Todo todo = new Todo(id , name, date);
    clonedTodos.add(todo);
    mTodos.setValue(clonedTodos);
}
...

Причина, по которой нам нужно сделать глубокую копию, прежде чем отправлять ее в объект mTodos LiveData, заключается в том, что представление должно поддерживать ссылку на предыдущее значение mTodos , чтобы сравнить его с новым значением (используется для вычисления выборочных изменений в представлении).

Ключом к этому решению является использование RecyclerView и служебного класса DiffUtil. При обновлении LiveData метод onChanged в MainActivity.java вызывается с новым List ‹Todo› (задачи).

На первом проходе код инициализирует RecyclerView (сохраняя ссылку на данные списка в mTodos) .

При последующих проходах код сравнивает предыдущий список (mTodos) с новым списком (todos) и отправляет изменения в RecyclerView.

MainActivity.java

...

final Observer<List<Todo>> todosObserver = new Observer<List<Todo>>() {
    @Override
    public void onChanged(@Nullable final List<Todo> todos) {
        if (mTodos == null) {
           mTodos = todos;
           mTodosAdapter = new TodosAdapter();
           todosRecyclerView.setAdapter(mTodosAdapter);
        } else {
            DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
                ...
            });
            result.dispatchUpdatesTo(mTodosAdapter);
            mTodos = todos;
        }
    }
};
mTodosViewModel.getTodos().observe(this, todosObserver);
...

Заключение

В конце концов, это решение оказалось довольно простым. В то же время, чтобы построить его, мне потребовалось много часов, пробираясь через различные примеры и документацию.

Надеюсь, что вы найдете ее полезной.

примечание: в другой статье я собираюсь изучить Room, чтобы еще больше упростить это приложение.