Страницы

Поиск по вопросам

вторник, 10 декабря 2019 г.

Перезапуск фрагмента из RecyclerView

#java #android #fragment_back_stack #fragment_manager #fragment_transaction


Разрабатываю приложение несколько месяцев, оно постепенно обрастает новыми добавлениями.
Вкратце - это интернет магазин, оформлен в виде одной активити с NavigationDrawer,
все страницы реализованы на фрагментах, в некоторых случаях в дело вступает backstack,
например в случае каталога, у которого есть 4 уровня вложенности - основные категории,
подкатегории, список продуктов, страница продукта. первые два оформлены в виде ListView,
третий в виде RecyclerView с GridLayoutManager. 

Метод onBackPressed в активити определен следующим образом 

@Override
public void onBackPressed() {

    ProductPageFragment ppf = (ProductPageFragment)  getFragmentManager().findFragmentByTag(Flags.Stack.PRODUCT_PAGE_FRAGMENT);
    Log.d("ppf", ppf + "");
    SubcatalogFragment sf = (SubcatalogFragment) getFragmentManager().findFragmentByTag(Flags.Stack.SUBCATALOG_FRAGMENT);
    Log.d("sf", sf + "");
    ProductGridFragment pf  = (ProductGridFragment) getFragmentManager().findFragmentByTag(Flags.Stack.PRODUCT_GRID_FRAGMENT);
    Log.d("pf", pf + "");
    CartMainFragment cmf = (CartMainFragment) getFragmentManager().findFragmentByTag(Flags.Stack.CART_FRAGMENT);
    Log.d("cmf", cmf + "");
    PurchaseFragment purf = (PurchaseFragment) getFragmentManager().findFragmentByTag(Flags.Stack.PURCHASE_FRAGMENT);
    Log.d("purf", purf + "");

    boolean sfVisible = sf != null && sf.isVisible();
    boolean ppfVisible = ppf != null && ppf.isVisible();
    boolean pfVisible = pf != null && pf.isVisible();
    boolean cmfVisible = cmf != null && cmf.isVisible();
    boolean purfVisible = purf != null && purf.isVisible();


    //If the fragment exists and has some back-stack entry
    if ((sfVisible || pfVisible || ppfVisible || cmfVisible || purfVisible) && getFragmentManager().getBackStackEntryCount()
> 0) {
        bpCount = 0;
        getFragmentManager().popBackStack();
    }

    //Else, nothing in the direct fragment back stack
    else {
        // Let super handle the back press
        bpCount++;
        switch (bpCount) {
            case 1:
                Toast.makeText(
                        getApplicationContext(),
                        "Для выхода нажмите \"Назад\" ещё раз",
                        Toast.LENGTH_SHORT
                ).show();
                break;
            case 2:
                super.onBackPressed();
                break;
        }
    }
}


bpCount, в данном случае, – счетчик нажатий на back, чтобы приложение не закрывалось
по первому нажатию.

На главной странице потребовалось разместить популярные продукты, использовал для
этого RecyclerView с GridLayoutManager и тем же viewholder, что и в списке продуктов.
По нажатию на итем должна открываться страница продукта. Запускаем приложение - видим
список продуктов, открываем страницу первого попавшегося продукта - отрисовывается
нормально. Нажимаем клавишу back, видим главную, открываем другой продукт (или этот
же, не имеет значения) и фрагмент не отрисовывается. если помещать его в контейнер
методом replace - то видим серый экран (стандартный фон) на месте контейнера, если
add, то RecyclerView отскролливается на выбранный продукт, при этом в обоих случаях
onCreateView фрагмета выполняется. 

Код, вызывающий фрагмент выглядит так:

private void showProductPage() {
    ProductPageFragment ppf = new ProductPageFragment();

    Bundle bundle = new Bundle();
    bundle.putParcelable(PRODUCT_TAG, product);

    ppf.setArguments(bundle);

    ((Activity) this.itemView.getContext()).getFragmentManager().beginTransaction()
            .replace(R.id.container, ppf, Flags.Stack.PRODUCT_PAGE_FRAGMENT)
            .addToBackStack(Flags.Stack.CATALOG_BACKSTACK)
            .commit();
}


this - это viewholder, itemView - view холдера, контекст является инстансом активити.

Мне бы хотелось понять, почему такое может происходить, ведь по первому клику фрагмент
отрисовывается нормально. И если кто нибудь подробно может мне объяснить разницу между
основными методами FragmentTransaction, то я тоже был бы очень признателен. Спасибо.

PS. Да, забыл, к чему я, собственно, расписывал как устроен каталог - во всем каталоге
нажатие на back отрабатывает нормально, а вот со страницей продукта происходит такая
же ерунда, как и при открытии его с главной. При первом запуске страница продукта отрисовывается,
а потом - нет.

PPS. Удалил лишние проверки из popBackStack, на всякий случай, ситуация не изменилась.
Вроде всё выполняется в одном потоке, но объявлял syncronized как метод, так и блок,
результат тот же. Мне не понятно, почему метод onCreateView выполняется, как и остальные
методы фрагмента, в  методе onResume вывожу isVisible и оно true, но при этом вьющка
не рисуется. Или может она рисуется где то ещё? Смотрим реализацию isVisible в базовом
классе фрагмента:

    final public boolean isVisible() {
        return isAdded() && !isHidden() && mView != null
                && mView.getWindowToken() != null && mView.getVisibility() == View.VISIBLE;
    }


Значит нам надо выяснить, как реализованы и что значат все эти условия. Ищем isAdded

    final public boolean isAdded() {
        return mActivity != null && mAdded; // Задается FragmentManager при добавлении
фрагмента
    }


isHidden:

    final public boolean isHidden() {
        return mHidden; // значение задается функциями showFragment и hideFragment
вызванные из экземляра FragmentManager
    }


mView это view фрагмента, который создаётся в onCreateView, 

view.getWindowToken выглядит так

    public IBinder getWindowToken() {
        return mAttachInfo != null ? mAttachInfo.mWindowToken : null;


// возвращет null если view не присоединено к контейнеру
        }
ну и View.VISIBLE означает, что view фрагмента отрисовывается. 

Следовательно ОС считает, что фрагмент у меня нарисован и существует. 
    


Ответы

Ответ 1



Не увидел в вашем коде используете ли вы рекомендованный паттерн OnFragmentInteraction - судя по обилию всяких условий в onBackPressed видимо нет, а зря. Смысл паттерна, в том, что корневой Activity должен реализовывать интерфейс отрабатывающий изменения в подчиненных фрагментах. Грубо говоря, если вы выбрали некий элемент списка, то корневой Activity должен быть в курсе этого, чтобы при возврате обратно отработать эти изменения. По-моему это плохая идея ловить onBackPressed - гораздо удобнее работать с onBackStackChanged примерно так: @Override public void onBackStackChanged() { Log.i(TAG, "Back stack changed!"); Log.i(TAG, "Stack size=" + getSupportFragmentManager().getBackStackEntryCount()); if(getSupportFragmentManager().getBackStackEntryCount()==0) {//we're on main //перерисовываем здесь } shouldDisplayHomeUp(); } Для перерисовки надо или дать знать фрагменту, что контент изменился - типа notifyDatasetChanged или просто перерисовать Cursor который лежит под фрагментом (если лежит). Можно попробовать отцепить и заново прицепить фрагмент: getSupportFragmentManager() .beginTransaction() .detach(contentFragment) .attach(contentFragment) .commit(); Говорят работает - честно скажу не пробовал.

Ответ 2



Попробуйте в адаптер передать ссылку на фрагмент, коий содержит ваш RecyclerView и вместо FragmentManager-а активити использовать FragmentManager фрагмента, который можно получить методом getChildFragmentManager() класса Fragment. Т.е. вместо ((Activity) this.itemView.getContext()).getFragmentManager() сделать instanceOfYoursFragmentThatHoldesRecycleViewWhichYouPassToAdaptersClass.getChildFragmentManager()

Комментариев нет:

Отправить комментарий