Обновление объекта комнаты и наблюдение за списком

#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 Отметьте, что эта конкретная запись в базе данных понравилась пользователю.

  1. На самом деле отправьте 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. 🙂 Спасибо!