Подтвердить что ты не робот

Уничтожьте элемент из адаптера ViewPager после изменения ориентации экрана

Итак, у меня возникла проблема с уничтожением (удалением) одной страницы из ViewPager после изменения ориентации экрана. Я попытаюсь описать проблему в следующих строках.

Я использую FragmentStatePagerAdapter для адаптера ViewPager и небольшой интерфейс, который описывает, как работает бесконечный просмотр пейджера. Идея этого в том, что вы можете прокручивать направо, пока не достигнете конца ViewPager. Если вы можете загружать больше результатов из вызова API, отображается страница прогресса до тех пор, пока результаты не придут.

Все хорошо до сих пор, теперь проблема возникает. Если во время этого процесса загрузки я поворачиваю экран (это не повлияет на вызов API, который в основном является AsyncTask), когда вызов возвращается, аварийное завершение приложения дает мне это исключение:

E/AndroidRuntime(13471): java.lang.IllegalStateException: Fragment ProgressFragment{42b08548} is not currently in the FragmentManager
E/AndroidRuntime(13471):    at android.support.v4.app.FragmentManagerImpl.saveFragmentInstanceState(FragmentManager.java:573)
E/AndroidRuntime(13471):    at android.support.v4.app.FragmentStatePagerAdapter.destroyItem(FragmentStatePagerAdapter.java:136)
E/AndroidRuntime(13471):    at mypackage.OutterFragment$PagedSingleDataAdapter.destroyItem(OutterFragment.java:609)

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

Вот код адаптера пейджера:

static class PagedSingleDataAdapter extends FragmentStatePagerAdapter implements
        IEndlessPagerAdapter {

    private WeakReference<OutterFragment> fragment;
    private List<DataItem> data;
    private SparseArray<WeakReference<Fragment>> currentFragments = new SparseArray<WeakReference<Fragment>>();

    private ProgressFragment progressElement;

    private boolean isLoadingData;

    public PagedSingleDataAdapter(SherlockFragment fragment, List<DataItem> data) {
        super(fragment.getChildFragmentManager());
        this.fragment = new WeakReference<OutterFragment>(
                (OutterFragment) fragment);
        this.data = data;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Object item = super.instantiateItem(container, position);
        currentFragments.append(position, new WeakReference<Fragment>(
                (Fragment) item));
        return item;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        currentFragments.put(position, null);
        super.destroyItem(container, position, object);
    }

    @Override
    public Fragment getItem(int position) {
        if (isPositionOfProgressElement(position)) {
            return getProgessElement();
        }

        WeakReference<Fragment> fragmentRef = currentFragments.get(position);
        if (fragmentRef == null) {
            return PageFragment.newInstance(args); // here I'm putting some info
                                                // in the args, just deleted
                                                // them now, not important
        }

        return fragmentRef.get();
    }

    @Override
    public int getCount() {
        int size = data.size();
        return isLoadingData ? ++size : size;
    }

    @Override
    public int getItemPosition(Object item) {
        if (item.equals(progressElement) && !isLoadingData) {
            return PagerAdapter.POSITION_NONE;
        }
        return PagerAdapter.POSITION_UNCHANGED;
    }

    public void setData(List<DataItem> data) {
        this.data = data;
        notifyDataSetChanged();
    }

    @Override
    public boolean isPositionOfProgressElement(int position) {
        return isLoadingData && position == data.size();
    }

    @Override
    public void setLoadingData(boolean isLoadingData) {
        this.isLoadingData = isLoadingData;
    }

    @Override
    public boolean isLoadingData() {
        return isLoadingData;
    }

    @Override
    public Fragment getProgessElement() {
        if (progressElement == null) {
            progressElement = new ProgressFragment();
        }
        return progressElement;
    }

    public static class ProgressFragment extends SherlockFragment {

        public ProgressFragment() {
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {

            TextView progressView = new TextView(container.getContext());
            progressView.setGravity(Gravity.CENTER_HORIZONTAL
                    | Gravity.CENTER_VERTICAL);
            progressView.setText(R.string.loading_more_data);
            LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT,
                    LayoutParams.FILL_PARENT);
            progressView.setLayoutParams(params);

            return progressView;
        }
    }
}

Обратный вызов onPageSelected() ниже, который в основном запускает вызов api, если необходимо:

 @Override
    public void onPageSelected(int currentPosition) {
        updatePagerIndicator(currentPosition);
        activity.invalidateOptionsMenu();
        if (requestNextApiPage(currentPosition)) {
            pagerAdapter.setLoadingData(true);
            requestNextPageController.requestNextPageOfData(this);
        }

Теперь также стоит сказать, что вызывает вызов API после предоставления результатов. Вот обратный вызов:

@Override
public boolean onTaskSuccess(Context arg0, List<DataItem> result) {
    data = result;
    pagerAdapter.setLoadingData(false);
    pagerAdapter.setData(result);
    activity.invalidateOptionsMenu();

    return true;
}

Итак, теперь, поскольку метод setData() вызывает notifiyDataSetChanged(), это вызовет getItemPosition() для фрагментов, которые в настоящее время находятся в массиве currentFragments. Конечно, для элемента progress он возвращает POSITION_NONE, так как я хочу удалить эту страницу, поэтому в основном вызывает обратный вызов destroyItem() из PagedSingleDataAdapter. Если я не поворачиваю экран, все работает нормально, но, как я сказал, если я поверну его, когда отображается элемент прогресса, а вызов API еще не закончен, обратный вызов destroyItem() будет вызван после действия перезапускается.

Возможно, мне также следует сказать, что я размещаю ViewPager в другом фрагменте, а не в действии, поэтому OutterFragment размещает ViewPager. Я создаю экземпляр pagerAdapter в обратном вызове onActivityCreated() OutterFragment и используя setRetainInstance(true), так что, когда экран вращается, pagerAdapter остается тем же (ничего не нужно менять, правильно?), Код здесь

if (pagerAdapter == null) {
    pagerAdapter = new PagedSingleDataAdapter(this, data);
}
pager.setAdapter(pagerAdapter);

if (savedInstanceState == null) {
    pager.setOnPageChangeListener(this);
    pager.setCurrentItem(currentPosition);
}

Подводя итог, ПРОБЛЕМА:

Если я попытаюсь удалить элемент прогресса из ViewPager после того, как он был создан, и действие было уничтожено и воссоздано (изменилась ориентация экрана), я получаю исключение выше (pagerAdapter остается тем же, поэтому все внутри он также остается тем же, ссылки и т.д., так как OutterFragment, который размещает pagerAdapter, не уничтожается, только отделяется от активности, а затем снова присоединяется). Возможно, что-то происходит с менеджером фрагментов, но я действительно не знаю, что.

Что я уже пробовал:

  • Попытка удалить фрагмент прогресса с использованием другого метода, то есть на обратном вызове onTaskSuccess() я пытался удалить фрагмент из диспетчера фрагментов, не работал.

  • Я также попытался скрыть элемент прогресса, а не полностью удалить его из диспетчера фрагментов. Это работало 50%, потому что представления больше не было, но у меня была пустая страница, так что на самом деле я не ищу того, что ищу.

  • Я также попытался (повторно) прикрепить progressFragment к менеджеру фрагментов после изменения ориентации экрана, это также не сработало.

  • Я также попытался удалить, а затем снова добавить фрагмент прогресса в менеджер фрагментов после того, как активность была воссоздана, не работает.

  • Пытался вызвать destroyItem() вручную из обратного вызова onTaskSuccess() (который действительно, действительно уродливый), но не работал.

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

Любое решение, рекомендация очень ценится.

Спасибо!

ОБНОВЛЕНИЕ: РЕШЕНИЕ НАЙДЕНО Хорошо, так что это заняло некоторое время. Проблема заключалась в том, что обратный вызов destroyItem() дважды вызывался на фрагменте выполнения, один раз, когда ориентация экрана изменилась, а затем снова после завершения вызова api. Вот почему исключение. Решение, которое я нашел, следующее: Продолжайте отслеживать, завершен ли вызов api или нет, и уничтожьте фрагмент прогресса именно в этом случае, код ниже.

@Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            if (object.equals(progressElement) && apiCallFinished == true) {
                apiCallFinished = false;
                currentFragments.put(position, currentFragments.get(position + 1));
                super.destroyItem(container, position, object);
            } else if (!(object.equals(progressElement))) {
                currentFragments.put(position, null);
                super.destroyItem(container, position, object);
            }
        }

а затем этот apiCallFinished установлен в false в конструкторе адаптера и равен true в обратном вызове onTaskSuccess(). И это действительно работает!

4b9b3361

Ответ 1

ОБНОВЛЕНИЕ: РЕШЕНИЕ НАЙДЕНО ОК, так что это заняло некоторое время. Проблема заключалась в том, что обратный вызов destroyItem() дважды вызывался на фрагменте выполнения, один раз, когда ориентация экрана изменилась, а затем снова после завершения вызова api. Вот почему исключение. Решение, которое я нашел, следующее: Продолжайте отслеживать, если вызов api закончен или нет, и уничтожьте фрагмент прогресса именно в этом случае, код ниже.

@Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            if (object.equals(progressElement) && apiCallFinished == true) {
                apiCallFinished = false;
                currentFragments.put(position, currentFragments.get(position + 1));
                super.destroyItem(container, position, object);
            } else if (!(object.equals(progressElement))) {
                currentFragments.put(position, null);
                super.destroyItem(container, position, object);
            }
        }

а затем этот apiCallFinished установлен в false в конструкторе адаптера и равен true в обратном вызове onTaskSuccess(). И это действительно работает!