#android #android-room #android-architecture-components #android-livedata
#Android #android-комната #android-архитектура-компоненты #android-livedata
Вопрос:
Представьте, что список сообщений (например, Facebook) представлен вам в основном действии. Основным «единственным источником истины» является база данных, а комната Android используется для извлечения и просмотра этого списка сообщений.
Вот как я наблюдаю за данными (подробности опущены из-за проблем с лицензией и краткости):
faViewModel = ViewModelProviders.of(getActivity()).get(FAViewModel.class);
faViewModel.getAllPosts().observe(getActivity(),
newPosts - > {
if (newPosts != null amp;amp; newPosts.size() > 0) {
postsLoadingProgressBar.setVisibility(View.INVISIBLE);
}
// if ((posts == null) || newPosts.get(0).getId() != posts.get(0).getId()) {
// Update the cached copy of the posts in the adapter.
posts = newPosts;
mPostsAdapter = new PostsAdapter(this.getChildFragmentManager(), newPosts);
mViewPager.setAdapter(mPostsAdapter);
// }
});
faViewModel.fetchNextData(currentPage);
Теперь у вас также есть кнопка «Мне нравится», прикрепленная к каждому сообщению, а также общее количество лайков, полученных одним сообщением.
Теперь пользователь нажимает на кнопку «Мне нравится», и вы отправляете действие. Это действие может выполнить следующее:
1.1 Выполните запрос на обновление, который увеличивает количество лайков, которые получил пост.
1.2 Отметьте, что эта конкретная запись в базе данных понравилась пользователю.
- На самом деле отправьте POST-запрос на сервер и обновите там артефакты. (Среди прочего, общее количество лайков и сообщений, которые понравились этому пользователю.)
Шаг 2 выполним, поэтому давайте не будем об этом говорить. Однако шаги 1.1 и 1.2 сложны, потому что, когда я отправляю свой запрос на обновление:
@Query("UPDATE post SET liked= :liked, likes = likes 1 WHERE id= :id")
void updateLike(int id, int liked);
(Примечание: это не заботится о антипатиях. Он работает как medium. Пользователь может поставить N лайков одному сообщению.)
В любом случае, когда я выполняю этот запрос, он обновляет базу данных. Поскольку я фактически наблюдаю за использованием этой базы LiveData
данных, я получаю новую сущность LiveData, которую затем подключаю к своему адаптеру. Мой адаптер считает (и правильно думает), что набор данных изменился, и, следовательно, он обновляет список. При этом он также прокручивается до первого сообщения в списке. В ЭТОМ ПРОБЛЕМА.
Как я могу это предотвратить?
Одно из возможных решений — проверить, совпадают ли идентификаторы записей входящего списка и текущего списка, не обновлять. Однако это означает, что я не получу обновленные No of likes
и другие данные из базы данных. Это, в свою очередь, означало бы, что, когда пользователь нажимает на кнопку «Мне нравится», мне придется вручную изменять No of likes
состояние объекта рисования и тому подобное В ПРЕДСТАВЛЕНИИ.
Это правильный подход? Или есть что-то, что я могу сделать, чтобы моя база данных оставалась «единственным источником истины«, сохраняя при этом функцию кнопки «Мне нравится».
Любые другие идеи / хаки приветствуются.
Комментарии:
1.
I'm actually observing this database using LiveData
как вы также наблюдаете заdatabase
общим кодом.2. @AbdulKawee обновил вопрос фрагментом кода.
Ответ №1:
Вы можете использовать DiffUtil
для решения вашей проблемы, если вы используете a RecyclerView.Adapter
, что означает, что у вас есть либо a RecyclerView
, либо a ViewPager2
.
Во-первых, вы не должны воссоздавать адаптер каждый раз, когда БД выдает новые данные.
Когда доступны новые данные, вы можете использовать DiffUtil.calculateDiff(DiffUtil.Callback cb)
для получения DiffUtil.DiffResult
объекта, а затем вызвать dispatchUpdatesTo(Adapter adapter)
результат.
Таким образом, все изменения между старыми и новыми данными будут применяться индивидуально. Например, скажем, что ваш старый список есть A,B,C
, если новый список будет A,X,B,C
добавлен только X
в RecyclerView (по умолчанию даже с анимацией).
Вот пример реализации:
class ListDiffCalculator : DiffUtil.Callback() {
private var oldList = emptyList<MyModel>()
private var newList = emptyList<MyModel>()
fun computeDiff(oldList: List<MyModel>, newList: List<MyModel>): DiffUtil.DiffResult {
this.oldList = oldList
this.newList = newList
return DiffUtil.calculateDiff(this)
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition].id == newList[newItemPosition].id
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition].hashCode() == newList[newItemPosition].hashCode()
}
Ваш адаптер может быть реализован Observer<List<MyModel>>
так, чтобы вы могли заставить его напрямую наблюдать за БД, опуская здесь другие материалы адаптера для простоты:
class Adapter : RecyclerView.Adapter<ViewHolder>(), Observer<List<MyModel>> {
private var data = emptyList<MyData>()
override fun onChanged(newData: List<MyModel>?) {
newData?.let {
val diff = listDiffCalculator.computeDiff(data, newData)
this.data = newData
diff.dispatchUpdatesTo(this)
}
}
}
И наблюдайте:
aViewModel.getAllPosts().observe(getActivity(), adapter)
Комментарии:
1. Спасибо за ваш ответ, @lelloman. Однако могу ли я использовать DiffUtil с FragmentStatePagerAdapter ViewPager?
2. Я так не думаю, я думал, что вы используете просмотр xD с помощью ViewPager2, хотя вы можете. Проблема в том, что RecyclerView. У адаптера есть методы для уведомления о конкретных изменениях, а не просто
notifyDatasetChanged
.3. О, здорово. Но это все еще в альфа-версии, и я никогда не получу одобрения на его использование в производстве прямо сейчас: P Спасибо за ваш ответ. Действительно полезно для будущих читателей.
4. Да, вы не должны использовать его прямо сейчас в производстве: D но, тем не менее, PagerAdapter не имеет API для уведомления отдельных изменений, поэтому вам придется взломать его.
5. Правильно. Круто, я думаю, что я как бы осуществлю хак, о котором я упоминал в вопросе. Вернемся к этому ответу, если у нас когда-нибудь появится возможность перейти на RecyclerView или ViewPager2. 🙂 Спасибо!