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.
... 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.
... 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.
... 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, чтобы еще больше упростить это приложение.