开发者

IllegalArgumentException: No view found for id for fragment when fast switching ActionBar Tabs

I am developing an Android app for tablets and not using the compatibility library.

There is just one Activity and it uses the ActionBar with 3 tabs. In the TabListener I use setContentView to load the layout specific to that tab, and then add the relevant fragments to their frames. This almost works exactly like I want, except when you switch between the tabs fast enough the app will crash.

I am using a Samsung Galaxy Tab as my debugging device and switching tabs is really fast. At a normal pace I can tap back and forth between them and the pages are loaded instantly. The problem is when I hyper switch between the tabs.

At first I got an

IllegalStateException: Fragment not added

as seen here: http://code.google.com/p/android/issues/detail?id=17029 Following the suggestion to use try/catch blocks in onTabUnselected, I made the app a little more robust, but that lead to the issue at hand:

IllegalArgumentException: No view found for id 0x... for fragment ...

I have not found any other case on the web of anyone else having the same issue, so I am concerned that I may be doing something that's not supported. Again, what I am trying to do is use 3 different layouts in one Activity - when you click on a tab, the listener will call setContentView to change the layout, and then add the fragments. It works beautifully unless you start aggressively switching between tabs.

I got the idea for this from: http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/FragmentTabs.html and instead of the TabListener keeping a reference to one fragment, I have an array of them. Also, I am not using attach/detach since those were just added in API 13.

My theory is that either setContentView hasn't finished creating the views, and that's why FragmentTransaction can't add them, OR the fragments are being added for one tab when another tab is selected and setContentView is called, destroying the other set of views.

I tried to hack something in to slow down tab switching but didn't get anywhere.

Here is the code for my TabListener:

private class BTabListener<T extends Fragment> implements ActionBar.TabListener{

    private int mLayout;
    private Fragment[] mFrags;
    private TabData mTabData;
    private Activity mAct;
    private boolean mNoNewFrags;


    public BTabListener(Activity act, int layout, TabData td, boolean frags){
        mLayout = layout;
        mTabData = td;
        mAct = act;
        mNoNewFrags = frags;

        mFrags = new Fragment[mTabData.fragTags.length];
        for(int i=0; i<mFrags.length; i++){
            //on an orientation change, this will find the fragments that were recreated by the system
            mFrags[i] = mAct.getFragmentManager().findFragmentByTag(mTabData.fragTags[i]);
        }

    }

    @Override
    public void onTabReselected(Tab tab, FragmentTransaction ft) {

    }

    @Override
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        //this gets called _after_ unselected
        //note: unselected wont have been called after an orientation change!
        //we also need to watch out because tab 0 always gets selected when adding the tabs

        //set the view for this tab
        mAct.setContentView(mLayout);

        for(int i=0; i<mFrags.length; i++){
            //this will be null when the tab is first selected
            if(mFrags[i]==null ){
                mFrags[i] = Fragment.instantiate(GUITablet.this, mTabData.classes[i].getName());                    
            }

            //if there was an orientation change when we were on this page, the fragment is already added
            if(!mNoNewFrags || mDefaultTab!=tab.getPosition()){
                ft.add(mTabData.containterIDs[i], mFrags[i], mTabData.fragTags[i]);
            }
        }
        mNoNewFrags = false;


    }

    @Override
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        // this gets called when another tab is selected, before it's onSelected method 

        for(Fragment f : mFrags){
            try{ //extra safety measure
                ft.remove(f);
            }catch(Exception e){
                e.printStackTrace();
                System.out.println("unselect couldnt remove");
            }
        }
    }

}

And finally, the stack trace:

09-29 01:53:08.200: ERROR/AndroidRuntime(4611): java.lang.IllegalArgumentException: No view found for id 0x7f0b0078 for fragment Fraggle{40ab2230 #2 id=0x7f0b0078 dummy2}
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:729)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:926)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at an开发者_运维知识库droid.app.BackStackRecord.run(BackStackRecord.java:578)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1226)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at android.app.FragmentManagerImpl$1.run(FragmentManager.java:374)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at android.os.Handler.handleCallback(Handler.java:587)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at android.os.Handler.dispatchMessage(Handler.java:92)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at android.os.Looper.loop(Looper.java:132)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at android.app.ActivityThread.main(ActivityThread.java:4028)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at java.lang.reflect.Method.invokeNative(Native Method)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at java.lang.reflect.Method.invoke(Method.java:491)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:844)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:602)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at dalvik.system.NativeStart.main(Native Method)

THANK YOU!!


Okay, found a way around this:

Put the references to the fragments in the layout files, and surrounded the setContentView call in onTabSelected in a try/catch block.

The exception handling took care of it!


I know it's a slightly old question but I was having the same and find a different work around.

The method itself is described here Avoid recreating same view when perform tab switching

but on the context of this specific crash, doing Show/Hide instead of add/replace avoids multiple fast calls to onCreateView on the fragment.

My final code ended up something like this:

@Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
if (fragments[tab.getPosition()] == null) {
     switch(tag.getPosition()){
        case 0: fragments[tab.getPosition()] = new // fragment for this position
        break;
        // repeat for all the tabs, for each `case`
     }
     fragmentTransaction.add(R.id.container, fragments[tab.getPosition()]);
}else{
     fragmentTransaction.show(fragments[tab.getPosition()]);
}
 }

@Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
if (fragments[tab.getPosition()] != null)
    fragmentTransaction.hide(fragments[tab.getPosition()]);
}


Seeing a similar case in a Google Play crash report.

java.lang.IllegalStateException:
    Fragment not added: MessageDisplayFragment{406e28f0 #2 id=0x7f0b0020}
at android.app.BackStackRecord.hide(BackStackRecord.java:397)
at org.kman.AquaMail.ui.AccountListActivity$UIMediator_API11_TwoPane.
    closeMessageDisplay(AccountListActivity.java:2585)

Relevant code:

AbsMessageFragment fragDisplay = (AbsMessageFragment)
    mFM.findFragmentById(R.id.fragment_id_message_display);

if (fragDisplay != null) {
    FragmentTransaction ft = mFM.beginTransaction();
    ft.setTransition(FragmentTransaction.TRANSIT_NONE);
    ft.show(some other fragment);
    ft.hide(fragDisplay);

^^ This is where it crashes

The fragment is definitely there (it was returned by findFragmentById), but calling hide() blew up with the "IllegalStateException: Fragment not added"

Not added? How's it not added if findFragmentById was able to find it?

All FragmentManager calls are being made from the UI thread.

The fragment being removed (fragDisplay) was added earlier and I'm sure that its FragmentTransaction was commited.


Fixed it by looping through my fragments, removing any of them which have been added and then adding the new one. Also check first that you're not trying to replace it with null or an existing fragment.

Edit: looks like it was just

getChildFragmentManager().executePendingTransactions();

that stopped it from crashing

private void replaceCurrentTabFragment(TabFragment tabFragment){

    if (tabFragment == null){ return; }

    if (tabFragment == getActiveTabFragment()){ return; }

    FragmentTransaction ft = getChildFragmentManager().beginTransaction();

    for (TabFragment fragment : mTabButtons.values()){
        if (((Fragment)fragment).isAdded()){
            ft.remove((Fragment)fragment);
        }
    }

    ft.add(R.id.fragment_frameLayout, (Fragment) tabFragment);
    ft.commit();
    getChildFragmentManager().executePendingTransactions();

}

private TabFragment getActiveTabFragment(){
    return (TabFragment) getChildFragmentManager().findFragmentById(R.id.fragment_frameLayout);
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜