D-Dᴙum
D-Dᴙum

Reputation: 7890

Android Example Code 'FragmentTabs' Causes Exception Upon Device Orientation

I have a need to use Tabs in my application and so have based my code around the Android FragmentTabs example found here.. However I've changed a couple of lines in the code to suit my own purpose which is as follows.

In the android code as given, every time the tab is changed by the user the Fragment going-out-of-view is destroyed and the Fragment coming into view is created. For my purposes this is NOT desirable. Although my Fragments can standalone from each other they react to real-time data coming through a network connection and act upon it. Just because the Fragment is out-of-view does NOT mean I want it to stop working in the background (which sadly seems to be the line of thinking in the Android example). Consequently I altered the code to 'hide' and 'show' tabs rather than create and destroy them.

To achieve this, in the example code I altered the onTabChanged() in two places as shown below:

public void onTabChanged(String tabId) {
        TabInfo newTab = mTabs.get(tabId);
        if (mLastTab != newTab) {
            FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
            if (mLastTab != null) {
                if (mLastTab.fragment != null) {
                    ft.hide(mLastTab.fragment);  //***** 1st Alteration
                    //ft.detach(mLastTab.fragment);
                }
            }
            if (newTab != null) {
                if (newTab.fragment == null) {
                    newTab.fragment = Fragment.instantiate(mActivity,
                            newTab.clss.getName(), newTab.args);
                    ft.add(mContainerId, newTab.fragment, newTab.tag);
                } else {
                    ft.show(newTab.fragment);   //************ 2nd Alteration
                    //ft.attach(newTab.fragment);
                }
            }

            mLastTab = newTab;
            ft.commit();
            mActivity.getSupportFragmentManager().executePendingTransactions();
        }
    }

This now works as I want it to. The problem now comes when the device is re-orientated. Upon the First orientation change, the previously selected tab content i.e. the Fragment is not shown. This is to do with the fact that I am 'hiding' and 'showing' tabs. However more seriously, upon the the second re-orientation of the the device an exception is thrown:

E/AndroidRuntime( 371): Caused by: java.lang.NullPointerException E/AndroidRuntime( 371): at android.support.v4.app.FragmentManagerImpl.findFragmentByTag(FragmentManager.java:1211) E/AndroidRuntime( 371): at $TabManager.addTab(ClassName.java:121)

The addTab method is here:

public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
        tabSpec.setContent(new DummyTabFactory(mActivity));
        String tag = tabSpec.getTag();

        TabInfo info = new TabInfo(tag, clss, args);

        // Check to see if we already have a fragment for this tab, probably
        // from a previously saved state.  If so, deactivate it, because our
        // initial state is that a tab isn't shown.
        FragmentManager fm = mActivity.getSupportFragmentManager();
        Log.i("Class Name","addTab() FragmentManager = " + fm.toString());
        Log.i("Class_Name","addTab() tag = " + tag);
        info.fragment = fm.findFragmentByTag(tag); // **** This causes the exception

        /*
         * Commented the following as Fragments are not removed but hidden/shown on tab change.
         */
        if (info.fragment != null && !info.fragment.isDetached()) {
            FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
            ft.detach(info.fragment);
            ft.commit();
        }

        mTabs.put(tag, info);
        mTabHost.addTab(tabSpec);
    }

More specifically, the exception is thrown at the info.fragment = fm.findFragmentByTag(tag); As can be seen in the code I've tried to isolate exactly what object is null i.e. is it the FragmentManager or the tag but these are not logged as null.

I don't fully understand the Android example and it works fine if I attach and detach Fragments instead of hiding and showing them. Can anyone explain what the problem is?

Upvotes: 0

Views: 656

Answers (1)

Iain
Iain

Reputation: 4203

As per the android docs on runtime changes, when the orientation changes your activity gets restarted. That means the FragmentManager saves its state into a bundle then reloads it.

All the tabs that have been looked at are added, even if they're not visible, so they get stashed away in the FragmentManager's list of added fragments. The TabManager doesn't store anything, though, it just gets created anew, and I suspect it's this mismatch between new TabManager and restored FragmentManager that's getting to you.

I recommend a careful reading of that doco and some very deliberate choices about saving/restoring of tabs.

The rest of this is pure speculation. It looks like neither Fragment.instantiate nor ft.add check to see if the new fragment duplicates an old one. That could be leading to a scenario where the TabManager holds a reference to one fragment and the FragmentManager holds two instances of that fragment.

It may be enough to clear the FragmentManager out of tabs that aren't currently visible in onCreate, or to check for fragment existence before creating a new instance.

Upvotes: 1

Related Questions