Android: как перерисовать фрагмент внутри ViewPager2? detach() attach() не работают

Столкнулся с небольшой сложностью. На всякий случай опишу ситуацию в целом, чтобы был понятен контекст.

Ситуация: есть активность, включающая в себя ViewPager, который перелистывает фрагменты. Каждый фрагмент содержит RecyclerView, реализует его адаптер и функцию кликов по элементам (CardView). Содержимое CardView берется из базы данных и в ходе работы активности может меняться в зависимости от действий пользователя. Далее обрисую очень схематично, просто чтобы понятна была суть.

Существует 2 сценария:

1 сценарий: пользователь кликает по CardView -> кликнутый CardView и вместе с ним еще несколько CardView на экране тут же меняют цвет

2 сценарий: пользователь кликает по CardView на текущем фрагменте -> несколько CardView на другом фрагменте меняют цвет

Задача: нужно поддерживать внешний вид всех фрагментов в актуальном состоянии на протяжении работы активности. Иными словами, чтобы пользователь получал результат сразу после клика на CardView, а не после перезапуска активности.

Все дело в том, что нужно не просто перехватить нажатие на CardView и обновить данные конкретно в нем, а заставить обновиться сразу несколько CardView. При этом, могут быть использованы наборы данных из другой таблицы бд (т.е. после нажатие может требоваться перезаполнять RecyclerView элементами CardView с данными уже из другой таблицы).

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

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

Как происходит обновление фрагмента:

public void restartFragment(Fragment fragment) {
    if(fragment!=null) {
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.detach(fragment);
        ft.attach(fragment);
        ft.commit();
    }
}

Схема работает, всех все устраивает, задача решена.

Но! Вернемся к самому началу. Есть ViewPager, и для его работы с фрагментами нужен адаптер. Первоначально использовался класс FragmentPagerAdapter, который уже порядочное время, как deprecated. Код требует поддержки, и встал вопрос о переходе на более актуальный вариант. Официальная документация рекомендует использовать ViewPager2 с адаптером FragmentStateAdapter, что и было сделано.

Проверяем работу. Приложение вызывает:

restartFragment(myFragment);

...и не происходит ничего. После клика на CardView, цвета не меняются => фрагмент не перерисовывается. Чтобы любые изменения вступили в силу, нужно перезапустить активность, только тогда фрагменты перерисуются с новыми данными.

Я начинаю разбираться, нагуглил разные варианты, самый рабочий вот этот:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.detach(fragment);
ft.commit();
ft = getSupportFragmentManager().beginTransaction();
ft.attach(fragment);
ft.commit();

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

Так же пробовал выставлять ft.setReorderingAllowed(false) и использовать commitNow() и commitAllowingStateLoss() вместо commit(), но ничего не работает. Понятно, что можно накрутить костылей, типа перелистывать страницу, обновлять, и возвращаться обратно автоматически и т.д., но хочется какое-то человеческое решение.

С этим и связан вопрос. Как теперь обновлять фрагмент, который находится в ViewPager2 непосредственно на экране? Буду крайне благодарен любой информации, советам, ссылкам, хоть на русском, хоть на английском.


UPD

В комментариях Yura Ivanov посоветовал не обновлять фрагмент и использовать notifyItemChanged() или notifyItemRangeChanged(). Выглядит перспективно, вот что получилось:

Структура отображения такая: ViewPager2 внутри него Fragment внутри него RecyclerView

Применение notify...Changed() к адаптеру RecyclerView внутри фрагмента не дает никакого эффекта при любых комбинациях.

Применение notify...Changed() к адаптеру ViewPager2 некоторые результаты дает. Действительно, ViewPager2 обновляется, но вот как это работает:

getSupportFragmentManager().beginTransaction().detach(fragment).commitNow();
getSupportFragmentManager().beginTransaction().attach(fragment).commitNow();
    
FragmentStateAdapter fSA = (FragmentStateAdapter) viewPager2.getAdapter();
fSA.notifyItemChanged(1);

Как уже писал чуть выше, если вместо attach-detach фрагмента обновлять изменения RecyclerView внутри фрагмента, то ничего не работает (хотя, по логике, как будто бы должно).

НО у этого решения есть 2 проблемы (и возможно они связаны):

  1. Оно работает долго. В старом ViewPager фрагмент обновлялся моментально, а тут нужно ждать около секунды, и для пользователя это выглядит, как будто приложение тормозит.

  2. При обновлении фрагмента (а точнее при notifyItemChanged()) появляется мерцание (типа fadeIn fadeOut), оно раздражает и вероятно с ним связана задержка.

Копнув глубже, я вычитал, что ViewPager2 вроде как построен по логике RecyclerView (по крайней мере, FragmentStateAdapter наследуется от RecyclerView.Adapter), и у RecyclerView при adapter.notify...Changed() тоже есть такая анимация мерцания. И там она убирается вот так:

((SimpleItemAnimator) myRecycler.getItemAnimator()).setSupportsChangeAnimations(false);

У ViewPager2 такого метода нет, и непонятно, как от этой анимации избавиться. На данном этапе решение чисто технически рабочее, но не до конца юзабельное. Осталось избавиться от мерцания с задержкой.


Ответы (0 шт):