Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NPE in Fragment.setUserVisibleHint() when using ViewPager

I am at a loss for this. I am switching tabs manually in my ViewPager. I have this code inside my Activity:

@Override
public void onBackPressed()
{
    if (childFragmentStack.empty())
    {
        // Go to the devices screen
        Intent intent = new Intent(this, SelectDeviceActivity.class);
        startActivity(intent);
    }
    else
    {
        Fragment fragment = childFragmentStack.pop();

        if (fragment == null)
        {
            return;
        }

        processingBackStack = true;

        if (fragment instanceof ViewChildFragment)
        {
            viewFragment.activateFragment((ViewChildFragment) fragment);
            mViewPager.setCurrentItem(VIEW_FRAGMENT_INDEX, true);
        }
        else if (fragment instanceof SetupChildFragment)
        {
            setupFragment.activateFragment((SetupChildFragment) fragment);
            mViewPager.setCurrentItem(SETUP_FRAGMENT_INDEX, true); //**
        }
        else if (fragment == homeFragment)
        {
            mViewPager.setCurrentItem(HOME_FRAGMENT_INDEX, true); //**
        }

        processingBackStack = false;
    }
}

If I scroll between tabs I add them to a Stack (the 'childFragmentStack'). I am using FragmentPagerAdapter to handle the fragments. What happens is if I do something like View->Setup->View->Setup and then reverse it, it only gets as far as Setup->View->CRASH. It's like when I press Back the Setup Fragment is no longer valid for what I'm doing, but it is never recreated! The Setup fragment is only created in MainActivity.onCreate(), so it should still be around and valid.

The NPE happens on the lines I marked **. Here is the full stack trace:

    04-18 16:04:57.096: E/AndroidRuntime(13072): FATAL EXCEPTION: main
    04-18 16:04:57.096: E/AndroidRuntime(13072): java.lang.NullPointerException
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.support.v4.app.Fragment.setUserVisibleHint(Fragment.java:841)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.support.v4.app.FragmentPagerAdapter.setPrimaryItem(FragmentPagerAdapter.java:130)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.support.v4.view.ViewPager.populate(ViewPager.java:1066)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:550)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:509)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.support.v4.view.ViewPager.setCurrentItem(ViewPager.java:501)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at com.lochinvar.serf.MainActivity.onBackPressed(MainActivity.java:234)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.app.Activity.onKeyUp(Activity.java:2131)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.view.KeyEvent.dispatch(KeyEvent.java:2633)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.app.Activity.dispatchKeyEvent(Activity.java:2361)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1819)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.view.ViewRootImpl.deliverKeyEventPostIme(ViewRootImpl.java:3577)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.view.ViewRootImpl.handleImeFinishedEvent(ViewRootImpl.java:3547)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.view.ViewRootImpl$ViewRootHandler.handleMessage(ViewRootImpl.java:2797)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.os.Handler.dispatchMessage(Handler.java:99)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.os.Looper.loop(Looper.java:137)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.app.ActivityThread.main(ActivityThread.java:4745)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at java.lang.reflect.Method.invokeNative(Native Method)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at java.lang.reflect.Method.invoke(Method.java:511)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at dalvik.system.NativeStart.main(Native Method)

[EDIT] I forgot to mention I overrode FragmentPagerAdapter.getPageTitle() and it never returns null (default case goes to a string).

like image 293
Mark Herscher Avatar asked Apr 18 '14 21:04

Mark Herscher


2 Answers

Finally! I'm now able to reliably recreate this error!

Another way that I found to recreate error is to close activity/app, and quickly reopen page with the ViewPager fragment. You may have to try a few times because in my tests I had to reopen the app within about 30ms. This time may be slower or faster for different speed devices.

The problem was that I only explicitly created the Fragment (using new) once, and kept a reference to that instance so that I could reuse it. One simple fix to this problem is to always return a new instance of the Fragment the FragmentPagerAdapter.getItem(...), as shown below.

public class ViewPagerAdapter extends FragmentPagerAdapter {
    ...

    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0: return mMyFragment; // Error. Has the edge-case crash.
            case 1: return new MyFragment(); // Works.
            default: return new MyDefaultFragment();
        }
    }
}

ps - The root problem likely has something to do with the Fragment lifecycle and trying to use it again while it's being destroyed.

pps - This solution fixes the NullPointerException for android.app.Fragment.setUserVisibleHint(Fragment.java:997) and should also work for android.support.v4.app.Fragment.setUserVisibleHint.

like image 88
Anonsage Avatar answered Nov 16 '22 02:11

Anonsage


This one was a real brain twister for me. We removed all of the code that replaced the Fragments and kept the same fragments for the entire lifecycle of the Activity and still had this problem. It wasn't until we viewPager.setOffscreenPageLimit(TABS); where TABS is the number of tabs (in our case 4) that we stopped getting the referenced NullPointerException.

FWIW -- I'm pretty sure the problem is in Google's code. We couldn't get this to fail on a Nexus 5 running Lollipop, but it fails across Samsung devices running Kitkat. When I traced through the error itself, it looked like the failure happens because the Fragment being referenced has already gone through the internal Fragment.initState function because the Fragment's id is -1.

like image 42
JohnnyLambada Avatar answered Nov 16 '22 02:11

JohnnyLambada