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

IllegalStateException: невозможно изменить идентификатор контейнера фрагмента

Платформа Android: 3.1

Я пытаюсь переместить фрагмент из контейнера A в контейнер B. Здесь следует код для выполнения этого:

private void reattach(int newContainerId, Fragment frag, String tag) {

      if (frag == null || !frag.isAdded() || (frag.getId() == newContainerId)) { return; }

      final FragmentManager fm = getFragmentManager();
      FragmentTransaction ft = fm.beginTransaction();
      ft.remove(frag);     //stacco il frammento dal container A
      ft.commit();
      fm.executePendingTransactions();

      ft = fm.beginTransaction();
      ft.add(newContainerId, frag, tag); //attacco il frammento sul container D
      ft.commit();
      fm.executePendingTransactions();   
   }

Когда я запускаю систему, я получаю следующее исключение IllegalStateException:

03-26 00:13:14.829: E/AndroidRuntime(30090): java.lang.RuntimeException: Unable to start activity ComponentInfo{eu.areamobile.apps.sfa/eu.areamobile.apps.sfa.activity.HomeActivity}: java.lang.IllegalStateException: Can't change container ID of fragment FragmentHomeController{408202a8 id=0x7f050010 HomeController}: was 2131034128 now 2131034132
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1751)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1767)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3117)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.ActivityThread.access$1600(ActivityThread.java:122)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1009)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.os.Handler.dispatchMessage(Handler.java:99)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.os.Looper.loop(Looper.java:132)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.ActivityThread.main(ActivityThread.java:4028)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at java.lang.reflect.Method.invokeNative(Native Method)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at java.lang.reflect.Method.invoke(Method.java:491)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:844)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:602)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at dalvik.system.NativeStart.main(Native Method)
03-26 00:13:14.829: E/AndroidRuntime(30090): Caused by: java.lang.IllegalStateException: Can't change container ID of fragment FragmentHomeController{408202a8 id=0x7f050010 HomeController}: was 2131034128 now 2131034132
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.BackStackRecord.doAddOp(BackStackRecord.java:338)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.BackStackRecord.add(BackStackRecord.java:316)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at eu.areamobile.apps.sfa.activity.HomeActivity.reattach(HomeActivity.java:340)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at eu.areamobile.apps.sfa.activity.HomeActivity.customHideShowCreate(HomeActivity.java:253)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at eu.areamobile.apps.sfa.activity.HomeActivity.customHideShowCreate(HomeActivity.java:155)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at eu.areamobile.apps.sfa.activity.HomeActivity.onPostCreate(HomeActivity.java:66)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.Instrumentation.callActivityOnPostCreate(Instrumentation.java:1111)
03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1734)

После быстрой отладки я заметил, что на Android 3.1 FragmentTransaction.remove не устанавливает 0 mContainerId удаляемого фрагмента, тогда как на ICS он работает правильно.
Любые предложения или обходные пути?

4b9b3361

Ответ 1

Мое решение этой проблемы - воссоздать фрагмент, сохраняя его состояние:

FragmentTransaction ft = mFragmentManager.beginTransaction();
ft.remove(old);
Fragment newInstance = recreateFragment(old);
ft.add(R.id.new_container, newInstance);
ft.commit();

Со следующей вспомогательной функцией:

private Fragment recreateFragment(Fragment f)
    {
        try {
            Fragment.SavedState savedState = mFragmentManager.saveFragmentInstanceState(f);

            Fragment newInstance = f.getClass().newInstance();
            newInstance.setInitialSavedState(savedState);

            return newInstance;
        }
        catch (Exception e) // InstantiationException, IllegalAccessException
        {
            throw new RuntimeException("Cannot reinstantiate fragment " + f.getClass().getName(), e);
        }
    }

Он работает для меня, по крайней мере, с последней библиотекой поддержки (r11), хотя я еще не тестировал много.

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

Ответ 2

после того, как вы попробовали все ответы на подобные вопросы, похоже, что я нашел способ сделать трюк.

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

Но это не работает каждый раз. Вторая проблема - это обратный стек. Это как-то блокирует транзакцию.

Итак, полный код, который работает для меня, выглядит так:

manager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
manager.beginTransaction().remove(detailFragment).commit();
manager.executePendingTransactions();
manager.beginTransaction()
    .replace(R.id.content, masterFragment, masterTag)
    .add(R.id.detail, detailFragment, activeTag)
    .commit();              

Ответ 3

У меня была аналогичная проблема, и вызов manager.executePendingTransactions() перед добавлением фрагмента во второй раз помогло. Спасибо!

Ответ 4

На основе Руководство разработчика фрагментов, в котором говорится:

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

Поэтому я предполагаю, что ваш объект frag уничтожается после вызовов

ft.remove(frag);     //stacco il frammento dal container A
ft.commit();

Но вы говорите, что он работает в ICS, и это странно. Почему бы вам не попробовать метод replace и посмотреть, что произойдет?

Надеюсь, что это поможет...

Ответ 5

Попробуйте использовать метод replace(). Если вы не используете commit дважды.

Ответ 6

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

Поскольку вы делаете все это из одного метода, вы можете попробовать выполнить удаление и добавить в один FragmentTransaction, чтобы вы знали, что он будет выполняться в том порядке, в котором вы хотите, и чтобы вы знали когда фрагмент был удален из одного контейнера и может добавить его в новый, а затем зафиксировать. Иначе, если вы действительно хотите сделать это как две отдельные транзакции, базовый класс Фрагмент имеет метод isRemoving(), который скажет вам, удаляется ли фрагмент из контейнера, и вы можете подождать, пока это не станет ложным, чтобы вы знали это были удалены. Тем не менее, вы начинаете вводить проблемы синхронизации, если теперь одна транзакция зависит от другой, прежде чем она сможет выполнить, и я не вижу причин не просто удалить и добавить как одну транзакцию.

Спасибо, Дэвид

Ответ 7

Есть ли какая-либо обоснованная причина, кроме плохих принципов программирования, что вы хотите сохранить ссылку на существующий фрагмент во всей деятельности или в любом случае? Вместо сохранения состояния фрагмента и просто его воссоздания?

Я создавал фрагменты, используя:

    public static ContactsFragment mContactsFragment;

    public static ContactsFragment getInstance() {
    if (mContactsFragment == null) {
        mContactsFragment = new ContactsFragment();
    }
    return mContactsFragment;
}

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

Почему бы вам:

    public static ContactsFragment newInstance(Context c) {
    ContactsFragment mContactsFragment = new ContactsFragment();
    mContext = c;
    return mContactsFragment;
}

и просто сохраните любую работу, которую вы сделали на фрагменте, и заново создайте ее, когда она вам понадобится? Возможно, я могу себе представить, создавали ли вы 100+ (новостное приложение?), Возможно, вы не захотите создавать их каждый раз, а хранить их в памяти? Кто-нибудь?