fixes NPE ActionBar.TabListener #402

Merged
merged 2 commits into from Mar 24, 2012

Conversation

Projects
None yet
3 participants
Contributor

gabrielittner commented Mar 23, 2012

This is a fix for Issue #391. The explanation is in the commit message.

For testing you can overwrite the FragmentTabs class in the fragment sample with the following code:

public class FragmentTabs extends SherlockFragmentActivity {

    ActionBar actionBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        setTheme(SampleList.THEME); //Used for theme switching in samples
        super.onCreate(savedInstanceState);

        actionBar = getSupportActionBar();
        actionBar.addTab(actionBar.newTab().setText("Simple")
            .setTabListener(new TabListener<FragmentStackSupport.CountingFragment>
            (this, "simple", FragmentStackSupport.CountingFragment.class)));
        actionBar.addTab(actionBar.newTab().setText("Contacts")
            .setTabListener(new TabListener<CursorLoaderListFragment>
            (this, "contacts", LoaderCursorSupport.CursorLoaderListFragment.class)));
        actionBar.addTab(actionBar.newTab().setText("Custom")
            .setTabListener(new TabListener<AppListFragment>
            (this, "custom", LoaderCustomSupport.AppListFragment.class)));
        actionBar.addTab(actionBar.newTab().setText("Throttle")
            .setTabListener(new TabListener<ThrottledLoaderListFragment>
            (this, "throttle", LoaderThrottleSupport.ThrottledLoaderListFragment.class)));
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    }

public static class TabListener<T extends Fragment> implements ActionBar.TabListener {
    private Fragment mFragment;
    private final Activity mActivity;
    private final String mTag;
    private final Class<T> mClass;

    public TabListener(Activity activity, String tag, Class<T> clz) {
        mActivity = activity;
        mTag = tag;
        mClass = clz;
    }

    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        if (ft == null) {
             Log.i("tabselected", "fragmenttransaction == null");
             return;
        }
        if (mFragment == null) {
            mFragment = Fragment.instantiate(mActivity, mClass.getName());
            ft.add(android.R.id.content, mFragment, mTag);
        } else {
            ft.attach(mFragment);
        }
    }

    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        if (ft == null) {
            Log.i("tabunselected", "fragmenttransaction == null");
                return;
        }
        if (mFragment != null) {
            ft.detach(mFragment);
        }
    }

    public void onTabReselected(Tab tab, FragmentTransaction ft) {
    }
}
}
@gabrielittner gabrielittner prevent NullPointerException at onTabSelected()
The implementation of onTabSelected() uses the instance variable 
mFragmentTransaction, which is hand over to the listeners 
onTabSelected(). At the start of an Activity, which uses a TabListener
a NullPointerException is thrown, because the FragmentTransaction is
null. The only point where a FragmentTransaction is assigned to 
mFragmentTransaction is onTabUnselected().

I guess the intended behavior is that the transaction created in
onTabUnselected() is used in onTabSelected() afterwards and the
detaching/attaching is done in the same transaction (because
onTabUnselected() doesn't call commit on the transaction itself). 

However at the start just onTabSelected() is called and not
onTabUnselected(), because there was nothing selected before. So
mFragmentTransaction is null and the Exception is thrown. I added an
if-check to  receive the FragmentTransaction the same way
onTabUnselected() does, when mFragmentTransaction is null.
e14a1c1
Owner

JakeWharton commented Mar 23, 2012

Can you add an instanceof check to the if statement as well? If someone is using tabs inside of a regular SherlockActivity you'll get a ClassCastException on the activity start.

JakeWharton merged commit a1c502f into JakeWharton:dev Mar 24, 2012

Hi to all :)
I don't really like this solution because if you rotate your device the onTabUnselected isn't called and after rotation you'll find two "lastVisibleFragment".

I have solved using directly the FragmentManager. For example for onTabSelected:

public void onTabSelected(Tab tab, FragmentTransaction ft) {
if (ft == null) {
Log.i(TAG, "FragmentTransaction == null during Tab selection");
return;
}
Fragment fragment = mFragmentManager.findFragmentByTag(mTag);
if(fragment == null) {
ft.add(android.R.id.content, Fragment.instantiate(mActivity, mClass.getName()), mTag);
} else {
ft.attach(fragment);
}
}

Is this a valid solution?

Thanks a lot (and thanks Jake for your great work!),
Simone

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment