About java.lang.IllegalStateException: Fragment already added solution

Keywords: Fragment Java Android

Preface
Recently, this bug has been found frequently in projects. Several solutions have been found on the internet, but the results are not very satisfactory. Now we will list all the modification schemes.

Background
The four tab page switching at the bottom of the project results in the tab switching scheme that adds four fragments to an Activity to manage dynamic hidden (), show (), add ().

Exception:

java.lang.IllegalStateException: Fragment already added: InvestmentFragment{44bb4a10 #1 id=0x7f0900d6 TAG1}
    at android.support.v4.app.FragmentManagerImpl.addFragment(FragmentManager.java:1197)
    at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:673)
    at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1489)
    at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:454)
    at android.os.Handler.handleCallback(Handler.java:733)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:136)
    at android.app.ActivityThread.main(ActivityThread.java:5291)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:849)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:665)
    at dalvik.system.NativeStart.main(Native Method)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

One way

Presumption: This exception is caused when a quick double-click call to the FragmentTransaction.add() method adds fragmentA, which is not generated separately each time. The FragmentTransaction.add() method is called internally in DialogFragment.show(), so this exception may also occur when the DialogFragment.show() method is called.

When adding () method, first judge fragmentA.isAdded(), the following call can avoid the exception:

if(!fragmentA.isAdded()){
                    FragmentManager manager = ((FragmentActivity)context).getSupportFragmentManager();
                    FragmentTransaction ft = manager.beginTransaction();
                    ft.add(fragmentA, "fragment_name");
                    ft.commit();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

After testing, this method can not solve the problem of adding fragments repeatedly, and fragmentA.isAdded() can not determine that fragments must add activities.

Mode 2: Since one method can't be controlled, add several more methods and one more.

getSupportFragmentManager().findFragmentByTag("TAG" + tagPage),adopt Tag Add the display corner mark
  if (!f.isAdded() && null == getSupportFragmentManager().findFragmentByTag("TAG" + tagPage){
add(R.id.main_fg_content, f, "TAG" + tagPage);
.......

}
Commit()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Test results: The same exception is thrown because if the method is executed twice quickly. The getSupportFragmentManager().findFragmentByTag("TAG"+tagPage) method is null. The reason is that the commit () method does not add(R.id.main_fg_content, f, "TAG"+tagPage) immediately after execution.
findFragmentByTag (xxx) cannot read content
Systematic explanation:

Schedules a commit of this transaction. The commit does not happen immediately; it will be scheduled as work on the main thread to be done the next time that thread is ready.
  • 1

The solution is to call
getSupportFragmentManager.executePendingTransactions()

Mode three

1. Due to the switching between front and back-end (activity stays in the background for too long and is recycled by the system), when the state is restored, the old fragent is replayed with the new Framgent and the old framgent still has problems in memory to be recycled.

System source code:

public void addFragment(Fragment fragment, boolean moveToStateNow) {
    if(this.mAdded == null) {
        this.mAdded = new ArrayList();
    }

    if(DEBUG) {
        Log.v("FragmentManager", "add: " + fragment);
    }

    this.makeActive(fragment);
    if(!fragment.mDetached) {
        if(this.mAdded.contains(fragment)) {
            throw new IllegalStateException("Fragment already added: " + fragment);
        }

        this.mAdded.add(fragment);
        fragment.mAdded = true;
        fragment.mRemoving = false;
        if(fragment.mHasMenu && fragment.mMenuVisible) {
            this.mNeedMenuInvalidate = true;
        }

        if(moveToStateNow) {
            this.moveToState(fragment);
        }
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

Later, when looking at the source code addFragment (), ArraList.Contains (xxFragment) judgment can basically exclude the front and back, multi-threading. Why do you say that the precondition for throwing an exception is that the same Fragmnet object is added twice, resulting in.
2. The difference between commit () and commitAllowingStateLoss () in FragmentTransaction is that commitAllowingStateLoss () in our project is suspected to be the use of commitAllowingStateLoss () to cause some state loss, resulting in inaccurate judgment of isAdded () and getSupportFragmentManager().findFragmentByTag("TAG"+tagPage).

Analysis: The following code was added to activity

@Override
    protected void onSaveInstanceState(Bundle outState) {
        // Super. onSave Instance State (outState); Bundle data is not saved
        if (outState != null) {//Bundle data exists, fragments are removed from the status preservation, and Fragme confusion is solved.
            String FRAGMENTS_TAG = "android:support:fragments";
            outState.remove(FRAGMENTS_TAG);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

The state is not saved, that is, it will not affect, even if the state is lost when commit (), the system will throw an exception, and the said state is the existing state saved in Fragment, such as the current location of a Tab, whether Btn can be clicked or not, it will never lose the value of member variables!!!

The reason is that the same Fragment object add () is created twice.  
(Unverified) Solution:

private void showFragment(Fragment f, int tagPage) {

    FragmentTransaction ft = fm.beginTransaction();
    if (!f.isAdded() && null == getSupportFragmentManager().findFragmentByTag("TAG" + tagPage)
            && isFristCreated) {
        if (showFg != null) {
            ft.hide(showFg).add(R.id.main_fg_content, f, "TAG" + tagPage);
        } else {
            ft.add(R.id.main_fg_content, f, "TAG" + tagPage);
        }
    } else { //It has been loaded into the container.
        if (showFg != null) {
            ft.hide(showFg).show(f);
        } else {
            ft.show(f);
        }
    }
    showFg = f;
    if (!isFinishing()) {
        ft.commitAllowingStateLoss();
        getSupportFragmentManager().executePendingTransactions();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

There is also a boolen value isFristCreated member variable, each time the new fragment object is true, otherwise false.

Fragment click Toggle

   @Override
    public void onClick(View v) {
        super.onClick(v);
        switch (v.getId()) {
               case R.id.main_rb_mine:
                if (mineFg == null) {
                    mineFg = new BMineFragment();
                    isFristCreated = true;
                } else {
                    isFristCreated = false;
                }
                break;

                .......
                .......
                .......
                }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

The reason for this writing guarantees that the existing conditions are valid and the logic is not questionable. And conform to the norms. Version Verification

This problem can be solved by verification.

       ft.commitAllowingStateLoss(); # commit () is solved in the same way
        getSupportFragmentManager().executePendingTransactions(). 
  • 1
  • 2

Quote:
One way http://blog.csdn.net/leeo1010/article/details/37934987 
Mode three http://blog.csdn.net/stoppig/article/details/31776607

Posted by laserlight on Fri, 21 Dec 2018 04:39:06 -0800