Android — проблемы с использованием FragmentActivity + Loader для обновления FragmentStatePagerAdapter

Я пытаюсь использовать FragmentActivity с ViewPager для отображения динамического списка Fragments. Есть много примеров того, как сделать статическую версию. Моя проблема в том, что список, который я отображаю, должен загружаться динамически, а также может изменяться в зависимости от ввода пользователя (добавлять/удалять). Я пытаюсь использовать настроенный android.support.v4.content.Loader для загрузки набора данных, которые я могу использовать для создания своего списка.

В моей настройке все идет нормально, пока я не дойду до точки, когда хочу обновить адаптер и выполнить вызов FragmentStatePagerAdapter#notifyDataSetChanged(), после чего этот код будет выполнен (из FragmentStatePagerAdapter)

public void finishUpdate(View container)
{
    if(mCurTransaction != null)
    {
        mCurTransaction.commit(); // BANG!!! The exception is thrown
        mCurTransaction = null;
        mFragmentManager.executePendingTransactions();
    }
}

Фиксация транзакции завершается с ошибкой с этим сообщением:

java.lang.IllegalStateException: Can not perform this action inside of onLoadFinished

потому что внутри FragmentManagerImpl выполняется этот код:

private void checkStateLoss() {
    if (mStateSaved) {
        throw new IllegalStateException(
                "Can not perform this action after onSaveInstanceState");
    }
    if (mNoTransactionsBecause != null) {
        throw new IllegalStateException(
                "Can not perform this action inside of " + mNoTransactionsBecause);
    }
}

Оказывается, значение mNoTransactionsBecause не равно null и оно устанавливается в LoaderManagerImpl.LoaderInfo, когда результат возвращается обратно в onLoadFinished

Я просматривал различные переменные, пытаясь каким-то образом изменить tramsaction.commit() на transaction.commitAllowingStateLoss(), но все, что связано с транзакцией, кажется закрытым или, по крайней мере, защищенным пакетом.

Может ли кто-нибудь дать мне общее представление, могу ли я делать то, что мне нужно (и как)?

Просто отметим, что мой код работает нормально, если вместо использования Loader я запускаю операции загрузки в AsyncTask.


person Bostone    schedule 12.10.2011    source источник
comment
Удалось ли вам найти правильный способ сделать это с помощью загрузчика? Я столкнулся с той же проблемой. Я смог заставить его работать с помощью transaction.commitAllowingStateLoss(), но все же было бы неплохо узнать, что Google намеревался сделать для нас... кажется, должен быть способ выполнить транзакцию фрагмента после загрузки данных с помощью загрузчика...   -  person ashughes    schedule 15.02.2012
comment
Обычно я отказался от этого и использую ModernTaskLoader с ограниченным пулом.   -  person Bostone    schedule 15.02.2012
comment
Что такое ModernTaskLoader? Нигде не нашел информации об этом...   -  person ashughes    schedule 17.02.2012
comment
Это из пакета совместимости v4   -  person Bostone    schedule 17.02.2012
comment
Хм... единственные два загрузчика, которые я вижу в пакете поддержки, это AsyncTaskLoader и CursorLoader :/   -  person ashughes    schedule 18.02.2012
comment
Исправление: android.support.v4.content.ModernAsyncTask.ModernAsyncTask   -  person Bostone    schedule 18.02.2012
comment
А, теперь вижу, спасибо. Это не общедоступный класс, поэтому он не отображается в официальной документации.   -  person ashughes    schedule 19.02.2012


Ответы (3)


У меня была очень похожая проблема, и я решил ее, используя обработчик фрагмента.

Я хотел показать диалоговое окно предупреждения onLoadFinished(), подобное следующему

public class MyFragment extends Fragment 
    implements LoaderManager.LoaderCallbacks<T> {

    public void onLoadFinished(Loader<T> loader, T data) {
        showDialog();
    }

    public void showDialog() {
        MyAlertDialogFragment fragment = new MyAlertDialogFragment();
        fragment.show(getActivity().getSupportFragmentManager(), "dialog");

    }
}

Но это дало ошибку

невозможно выполнить это действие внутри onLoadFinished

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

private Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        if(msg.what == MSG_SHOW_DIALOG) {
            showDialog();
        }
    }
};

А затем измените onLoadFinished(...), чтобы вместо этого отправить сообщение обработчику

   public void onLoadFinished(Loader<T> loader, T data) {
        handler.sendEmptyMessage(MSG_SHOW_DIALOG);
    }
person Ian Warwick    schedule 11.04.2012
comment
Или вы отправляете runnable в обработчик. Handler h = new Handler(Looper.getMainLooper()); h.post(new Runnable(){ .... }); и т. д. - person Chris.Jenkins; 03.08.2012
comment
Реализация @Chris.Jenkins Яна более эффективна, поскольку требует только одного выделения объекта. :) - person Alex Lockwood; 12.08.2013
comment
@AlexLockwood На самом деле это не проблема, если вы не создаете 1000 объектов за один раз в высокопроизводительном сценарии. - person Chris.Jenkins; 12.08.2013
comment
Также обратите внимание, что это решение позволяет избежать исключения. На самом деле это не предотвращает потерю состояния фрагмента. Если onLoadFinished() вызывается, runnable публикуется, а код DialogFragment#show() выполняется после вызова onSaveInstanceState() (что бывает редко, но, вероятно, все же возможно), то состояние DialogFragment не будет запоминаться при последующем восстановлении состояния Activity. В общем, вы, вероятно, захотите избежать отображения диалогов в onLoadFinished() (в любом случае это плохо для пользователя). - person Alex Lockwood; 12.08.2013
comment
Просто на голову! Это работает с FragmentStatePagerAdapter и не работает с FragmentPagerAdapter. К счастью, OP использовал правильный. Но мне пришлось потратить на это несколько часов. - person sha256; 09.08.2014
comment
Это на самом деле делает вещи хуже! Загрузчик потрудился вызвать onLoadFinished для правильного экземпляра фрагмента. Затем вы неявно сослались на этот экземпляр через handler.sendEmptyMessage();. Между отправкой этого сообщения и его обработкой ваш фрагмент/активность может быть уничтожен из-за изменения конфигурации, и вы в конечном итоге запустите это не в том экземпляре. В основном вам лучше использовать commitAllowingStateLoss - person Sam; 02.03.2015

Вы можете скопировать FragmentStatePagerAdapter и изменить его по своему усмотрению или создать свой собственный PagerAdapter с нуля. Или просто не используйте загрузчик.

person Nikolay Elenkov    schedule 13.10.2011
comment
Это не проблема адаптера, все, что он делает - фиксирует транзакцию. Но кто-то приложил значительные усилия, чтобы предотвратить фиксацию транзакции в onLoadFinished. Я хочу знать, почему? Я также не люблю переписывать классы ради одной строки. И мне нравится концепция Loader, и я хочу ее использовать. Как я уже сказал, у меня есть работающая реализация напрямую с использованием AsyncTask. - person Bostone; 13.10.2011
comment
По-видимому, потому что существует некоторая вероятность потери состояния при вызове onLoadFinished(). Из JavaDoc: «Обратите внимание, что обычно приложению не разрешается фиксировать транзакции фрагментов во время этого вызова, поскольку это может произойти после сохранения состояния активности. См. FragmentManager.openTransaction() для дальнейшего обсуждения этого вопроса. ' Вы должны либо перейти на адаптер, чтобы использовать commitAllowingStateLoss(), либо отказаться от загрузчика. У меня была та же проблема, и вот ответ, который я получил: группы .google.com/forum/#!topic/android-developers/dXZZjhRjkMk/ - person Nikolay Elenkov; 13.10.2011

Он работает с использованием rootView.post(). Просто определите переменную rootView в области класса, а затем раздуйте ее onCreateView(). Наконец, onLoadFinished() просто используйте его следующим образом:

            rootView.post(new Runnable() {
                public void run() {
                    General.showFragment();
                }

            });
person Ibrahim    schedule 07.09.2015