IllegalStateException: невозможно изменить идентификатор контейнера для фрагмента - PullRequest
26 голосов
/ 28 марта 2012

Платформа 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 этоработает правильно.
Есть предложения или обходные пути?

Ответы [ 10 ]

19 голосов
/ 19 февраля 2013

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

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), хотя я еще не тестировал много.

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

16 голосов
/ 24 января 2014

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

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

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

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

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();              
2 голосов
/ 05 марта 2015

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

1 голос
/ 23 августа 2018

Это может произойти при попытке добавить один и тот же fragment более одного раза.

    public void populateFragments() {
        Fragment fragment = new Fragment();
        //fragment is added for the first time
        addFragment(fragment);

        // fragment is added for the second time
        // This call will be responsible for the issue. The 
        addFragment(fragment);
    }

    public void addFragment(Fragment fragment) {
        FrameLayout frameLayout = AppView.createFrameLayout(context);
        view.addView(frameLayout);
        getSupportFragmentManager().beginTransaction().add(frameLayout.getId(), fragment).commit();
    }
0 голосов
/ 25 марта 2019

Я обнаружил, что проблема в том, что мне потребовались две транзакции Fragment.Я пытался удалить () и совершить () дважды на одном и том же.

 // create new fragment
 MyFragment myNewFragment = MyFragment.newInstance(this, data, true);
 FragmentManager fm = this.getSupportFragmentManager();
 // clear the back stack
 fm.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);

 //fragmentTransaction is created for the removal of the old
 FragmentTransaction fragmentTransaction = fm.beginTransaction();
 fragmentTransaction.remove(myOldFragment);
 fragmentTransaction.commit();
 fm.executePendingTransactions();

 // fragmentTransaction2 is created to add the new one
 FragmentTransaction fragmentTransaction2 = fm.beginTransaction();
 fragmentTransaction2.add(R.id.fragment_frame, myNewFragment);
 fragmentTransaction2.commit();
 fm.executePendingTransactions();
0 голосов
/ 28 февраля 2018

Итак, у меня возникла та же проблема, и я решил ее другим способом (хотя бы немного). Мое приложение имеет 4 фрагмента, и мне нужно управлять, когда мое устройство находится в ландшафтном режиме.

позвольте мне объяснить мое приложение и решение:

Мое приложение у меня есть 4 фрагмента в моем приложении: 1 - Рецепты (которые содержат список рецептов) 2 - Ингредиенты (список ингредиентов из выбранного рецепта) 3 - Шаги (список шагов) 4 - Player (фрагмент, который вызывается для воспроизведения видео)

В портретном режиме У меня не было проблем, все отлично работает!

Пейзаж В моей деятельности есть 2 контейнера для моих фрагментов, один слева с моим списком рецептов, а другой справа. Этот контейнер справа будет динамически меняться.

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

Мое решение

  1. Сохранить последний фрагмент, показанный в переменной;
  2. Сохранить фрагмент состояния в SavedInstance;
  3. В onCreate извлекают фрагмент и последний фрагмент показывается.

Итак, чтобы быть более конкретным, я создал эту суть, чтобы помочь https://gist.github.com/jillesRagonha/4c184165b92fdf026ab3cd033b64b1bf

надеюсь, это поможет кому-нибудь: D

0 голосов
/ 01 марта 2016

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

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

    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+ (новостное приложение?), Вам, возможно, не нужно создавать их каждый раз, а хранить их в памяти?Кто-нибудь? * * 1013

0 голосов
/ 16 мая 2012

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

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

Спасибо, David

0 голосов
/ 14 мая 2012

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

0 голосов
/ 10 мая 2012

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...