Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NullPointerException on rotation during dispatchCreateOptionsMenu, stack trace doesn't include any functions in my app

I have an app that uses tabs on the action bar in ICS, where each tab has a fragment inside it. Under certain circumstances, after I've pushed a button on the options menu on the action bar, then rotate the device, I get a NullPointerException. I can reproduce it reliably with the same set of steps, but there are some cases (like if I don't push any buttons on the action bar) do not produce the exception. The exception doesn't seem to reference any line in my code, and occurs during recreation of the activity after an orientation change.

Here's the exception:

09-18 20:56:22.357: E/AndroidRuntime(689): FATAL EXCEPTION: main
09-18 20:56:22.357: E/AndroidRuntime(689): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.andavapps.flightbot/com.andavapps.flightbot.FlightBotActivity}: java.lang.NullPointerException
09-18 20:56:22.357: E/AndroidRuntime(689):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1956)
09-18 20:56:22.357: E/AndroidRuntime(689):     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1981)
09-18 20:56:22.357: E/AndroidRuntime(689):     at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3351)
09-18 20:56:22.357: E/AndroidRuntime(689):     at android.app.ActivityThread.access$700(ActivityThread.java:123)
09-18 20:56:22.357: E/AndroidRuntime(689):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1151)
09-18 20:56:22.357: E/AndroidRuntime(689):     at android.os.Handler.dispatchMessage(Handler.java:99)
09-18 20:56:22.357: E/AndroidRuntime(689):     at android.os.Looper.loop(Looper.java:137)
09-18 20:56:22.357: E/AndroidRuntime(689):     at android.app.ActivityThread.main(ActivityThread.java:4424)
09-18 20:56:22.357: E/AndroidRuntime(689):     at java.lang.reflect.Method.invokeNative(Native Method)
09-18 20:56:22.357: E/AndroidRuntime(689):     at java.lang.reflect.Method.invoke(Method.java:511)
09-18 20:56:22.357: E/AndroidRuntime(689):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
09-18 20:56:22.357: E/AndroidRuntime(689):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
09-18 20:56:22.357: E/AndroidRuntime(689):     at dalvik.system.NativeStart.main(Native Method)
09-18 20:56:22.357: E/AndroidRuntime(689): Caused by: java.lang.NullPointerException
09-18 20:56:22.357: E/AndroidRuntime(689):     at android.app.FragmentManagerImpl.dispatchCreateOptionsMenu(FragmentManager.java:1831)
09-18 20:56:22.357: E/AndroidRuntime(689):     at android.app.Activity.onCreatePanelMenu(Activity.java:2445)
09-18 20:56:22.357: E/AndroidRuntime(689):     at com.android.internal.policy.impl.PhoneWindow.preparePanel(PhoneWindow.java:388)
09-18 20:56:22.357: E/AndroidRuntime(689):     at com.android.internal.policy.impl.PhoneWindow.invalidatePanelMenu(PhoneWindow.java:739)
09-18 20:56:22.357: E/AndroidRuntime(689):     at com.android.internal.policy.impl.PhoneWindow.restorePanelState(PhoneWindow.java:1664)
09-18 20:56:22.357: E/AndroidRuntime(689):     at com.android.internal.policy.impl.PhoneWindow.restoreHierarchyState(PhoneWindow.java:1619)
09-18 20:56:22.357: E/AndroidRuntime(689):     at android.app.Activity.onRestoreInstanceState(Activity.java:906)
09-18 20:56:22.357: E/AndroidRuntime(689):     at android.app.Activity.performRestoreInstanceState(Activity.java:878)
09-18 20:56:22.357: E/AndroidRuntime(689):     at android.app.Instrumentation.callActivityOnRestoreInstanceState(Instrumentation.java:1100)
09-18 20:56:22.357: E/AndroidRuntime(689):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1934)
09-18 20:56:22.357: E/AndroidRuntime(689):     ... 12 more

And here's my activity code (removed some unrelated code for simplicity of showing it here)

public class MyActivity extends Activity {

    private class MyTabListener<C extends MyFragment> implements ActionBar.TabListener {

        private Activity activity;
        private MyFragment fragmentMain;
        private MyFragment fragmentSide;
        private Class<C> cls;

        public MyTabListener(Activity a, Class<C> c) {
            activity = a;
            cls = c;
        }

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

        @Override
        public void onTabSelected(Tab tab, FragmentTransaction ft) {
            if (sidebar) {
                if (fragmentSide == null) {
                    fragmentSide = (MyFragment) Fragment.instantiate(activity, cls.getName());
                    ft.add(c.SIDE_FRAME, fragmentSide, fragmentSide.getViewTag());
                } else
                    ft.attach(fragmentSide);
            } else {
                if (fragmentMain == null) {
                    fragmentMain = (MyFragment) Fragment.instantiate(activity, cls.getName());
                    ft.add(c.MAIN_FRAME, fragmentMain, fragmentMain.getViewTag());
                } else
                    ft.attach(fragmentMain);
            }
            selected = tabs.indexOf(tab);
            mainUp = false;
            if (setUpComplete)
                invalidateOptionsMenu();
        }

        @Override
        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
            if (fragmentSide != null && !fragmentSide.isDetached())
                ft.detach(fragmentSide);
            if (fragmentMain != null && !fragmentMain.isDetached())
                ft.detach(fragmentMain);
        }

    }

    private class MyMainTabListener<C extends MyFragment> implements ActionBar.TabListener {

        private Activity activity;
        private MyFragment fragmentMain;
        private Class<C> cls;

        public MyMainTabListener(Activity a, Class<C> c) {
            activity = a;
            cls = c;
        }

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

        @Override
        public void onTabSelected(Tab tab, FragmentTransaction ft) {
            if (fragmentMain == null) {
                fragmentMain = (MyFragment) Fragment.instantiate(activity, cls.getName());
                ft.add(c.MAIN_FRAME, fragmentMain, fragmentMain.getViewTag());
            } else if (fragmentMain.isDetached())
                ft.attach(fragmentMain);
            mainUp = !sidebar;
            if (setUpComplete)
                invalidateOptionsMenu();
        }

        @Override
        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
            if (!sidebar && fragmentMain != null && !fragmentMain.isDetached())
                ft.detach(fragmentMain);
            else if (sidebar && (fragmentMain == null || fragmentMain.isDetached())) {
                if (fragmentMain == null) {
                    fragmentMain = (MyFragment) Fragment.instantiate(activity, cls.getName());
                    ft.add(c.MAIN_FRAME, fragmentMain, fragmentMain.getViewTag());
                } else if (fragmentMain.isDetached())
                    ft.attach(fragmentMain);
            }
        }

    }

    private static final class c {
        //A bunch of constants are defined here
    }

    private ArrayList<ActionBar.Tab> tabs = new ArrayList<ActionBar.Tab>();

    private int side;
    private int orient;
    private int selected;
    private boolean sidebar;
    private boolean mainUp;
    private boolean lock;
    private boolean setUpComplete = false;

    @Override
    public void onCreate(Bundle inState) {
        super.onCreate(inState);
        setContentView(R.layout.main_rel);

        orient = detectOrientation();

        if (inState != null) {
            selected = inState.getInt("selected", 1);
            side = inState.getInt("side", c.LEFT_SIDE);
            sidebar = inState.getBoolean("visible", true);
            mainUp = inState.getBoolean("mainup", !sidebar);
            lock = inState.getBoolean("lock", false);
        } else {
            selected = 1;
            side = c.LEFT_SIDE;
            sidebar = true;
            mainUp = false;
            lock = false;
        }

        ActionBar ab = getActionBar();
        ab.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        tabs.add(ab.newTab().setText(MainFragment.getTabText()).setTabListener(new MyMainTabListener<MainFragment>(this, MainFragment.class)));
        tabs.add(ab.newTab().setText(OtherFragment.getTabText()).setTabListener(new MyTabListener<OtherFragment>(this, OtherFragment.class)));
        tabs.add(ab.newTab().setText(AnotherFragment.getTabText()).setTabListener(new MyTabListener<AnotherFragment>(this, AnotherFragment.class)));
        tabs.add(ab.newTab().setText(YetAnotherFragment.getTabText()).setTabListener(new MyTabListener<YetAnotherFragment>(this, YetAnotherFragment.class)));
    }

    @Override
    protected void onStart() {
        super.onStart();

        if (!setUpComplete)
            setUp();
    }

    @Override
    protected void onResume() {
        super.onResume();

        if (!setUpComplete)
            setUp();
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    protected void onStop() {
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        setUpComplete = false;
        getActionBar().removeAllTabs();

        super.onSaveInstanceState(outState);
        outState.putInt("selected", selected);
        outState.putInt("side", side);
        outState.putBoolean("visible", sidebar);
        outState.putBoolean("mainup", mainUp);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu, menu);
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        menu.findItem(R.id.menu_showhide).setTitle(c.MENU_SHOW_TEXT[(sidebar ? 1 : 0)]).setIcon(c.MENU_SHOW_DRAW[(sidebar ? 1 : 0)][side][orient][(mainUp ? 0 : 1)]);
        menu.findItem(R.id.menu_swapside).setTitle(c.MENU_SIDE_TEXT[side][orient]).setIcon(c.MENU_SIDE_DRAW[side][orient]);
        menu.findItem(R.id.menu_lock).setTitle(c.MENU_LOCK_TEXT[(lock ? 1 : 0)]).setIcon(c.MENU_LOCK_DRAW[(lock ? 0 : 1)]);
        if (!sidebar)
            menu.findItem(R.id.menu_swapside).setVisible(false).setEnabled(false);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.menu_swapside:
                toggleSide();
                return true;
            case R.id.menu_showhide:
                toggleVisibility();
                return true;
            case R.id.menu_lock:
                toggleRotation();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    private int detectOrientation() {
            int o = getResources().getConfiguration().orientation;
            int r = getWindowManager().getDefaultDisplay().getRotation();

            if (o == Configuration.ORIENTATION_LANDSCAPE && (r == Surface.ROTATION_0 || r == Surface.ROTATION_90))
                return c.LAND_ORIENT;
            else if (o == Configuration.ORIENTATION_PORTRAIT && (r == Surface.ROTATION_90 || r == Surface.ROTATION_180))
                return c.RPORT_ORIENT;
            else if (o == Configuration.ORIENTATION_LANDSCAPE && (r == Surface.ROTATION_180 || r == Surface.ROTATION_270))
                return c.RLAND_ORIENT;
            else
                return c.PORT_ORIENT;
    }

    private void toggleVisibility() {
        sidebar = !sidebar;
        mainUp = !sidebar;
        invalidateOptionsMenu();
        setUpTabs();
    }

    private void toggleSide() {
        side = (side == c.RIGHT_SIDE ? c.LEFT_SIDE : c.RIGHT_SIDE);
        invalidateOptionsMenu();
        setUpSide();
    }

    private void toggleRotation() {
        lock = !lock;
        invalidateOptionsMenu();
        setUpLock();
    }

    private void setUp() {
        setUpTabs();
        setUpSide();
        setUpLock();
        setUpComplete = true;
    }

    private void setUpTabs() {
        ActionBar ab = getActionBar();
        ab.removeAllTabs();

        ab.addTab(tabs.get(0), sidebar || mainUp);
        if (sidebar)
            ab.removeTab(tabs.get(0));
        for (int i = 1; i < tabs.size(); i ++)
            ab.addTab(tabs.get(i), !mainUp && selected == i);
    }

    private void setUpSide() {
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
        params.addRule(c.SIDE_RULE[side][orient], c.SIDE_FRAME);
        FrameLayout mf = (FrameLayout) findViewById(c.MAIN_FRAME);
        mf.setLayoutParams(params);

        params = new RelativeLayout.LayoutParams(c.LAYOUT_WIDTH[orient], c.LAYOUT_HEIGHT[orient]);
        params.addRule(c.ALIGN_RULE[side][orient]);
        FrameLayout sf = (FrameLayout) findViewById(c.SIDE_FRAME);
        sf.setLayoutParams(params);
    }

    private void setUpLock() {
        setRequestedOrientation((lock ? c.LOCK_ORIENT[orient] : ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED));
    }

}

A couple notes about my app and the code to explain things:

  • The app displays a main fragment and a sidebar fragment
  • The options menu contains three buttons: One to switch the sidebar from one side of the screen to the other, one to hide the sidebar, and one to lock the orientation
  • The main fragment is always the first in the tab list and is always of type MainFragment
  • I'm running this on two devices running ICS (Asus Trans Prime, 4.0.4; HTC Vivid, 4.0.3) & the Emulator (ICS 4.0.3 & JB 4.1). This only happens on ICS.

The exception occurs with the following sequence:

  • Launch app
  • press button to hide sidebar
  • rotate device

If anything else happens before rotating the device, the exception doesn't occur. For example if the sidebar is unhidden, i don't get the exception. If the device is rotated first, the exception will never occur, so even if the sidebar is hidden, and the device is rotated again, I don't get the exception. And the stack trace doesn't reference a single function in my code, so I can even seem to locate the root cause.

It seems this is the function in FragmentManager.java (package android.app) that throws the exception:

1827     public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) {
1828         boolean show = false;
1829         ArrayList<Fragment> newMenus = null;
1830         if (mActive != null) {
1831             for (int i=0; i<mAdded.size(); i++) {
1832                 Fragment f = mAdded.get(i);
1833                 if (f != null && !f.mHidden && f.mHasMenu && f.mMenuVisible) {
1834                     show = true;
1835                     f.onCreateOptionsMenu(menu, inflater);
1836                     if (newMenus == null) {
1837                         newMenus = new ArrayList<Fragment>();
1838                     }
1839                     newMenus.add(f);
1840                 }
1841             }
1842         }
1843         
1844         if (mCreatedMenus != null) {
1845             for (int i=0; i<mCreatedMenus.size(); i++) {
1846                 Fragment f = mCreatedMenus.get(i);
1847                 if (newMenus == null || !newMenus.contains(f)) {
1848                     f.onDestroyOptionsMenu();
1849                 }
1850             }
1851         }
1852         
1853         mCreatedMenus = newMenus;
1854         
1855         return show;
1856     }

There's no null check on mAdded before trying to use it. The same function in JB replaces (mActive != null) with (mAdded != null). But I have no idea what I might do for ICS as a workaround to avoid this.

Anybody have any ideas? I've scoured StackOverflow looking for similar issues, but come up empty so far. Thanks! If there's anything else I need to post, let me know and I'll add it.

like image 804
bjg222 Avatar asked Sep 18 '12 17:09

bjg222


People also ask

How do I fix NullPointerException?

NullPointerException is thrown when a reference variable is accessed (or de-referenced) and is not pointing to any object. This error can be resolved by using a try-catch block or an if-else condition to check if a reference variable is null before dereferencing it.

Why am I getting a NullPointerException?

What Causes NullPointerException. The NullPointerException occurs due to a situation in application code where an uninitialized object is attempted to be accessed or modified. Essentially, this means the object reference does not point anywhere and has a null value.

What does NullPointerException mean in processing?

NullPointerException is thrown when an application attempts to use an object reference that has the null value. These include: Calling an instance method on the object referred by a null reference. Accessing or modifying an instance field of the object referred by a null reference.

How can we avoid NullPointerException in Java with example?

Answer: Some of the best practices to avoid NullPointerException are: Use equals() and equalsIgnoreCase() method with String literal instead of using it on the unknown object that can be null. Use valueOf() instead of toString() ; and both return the same result. Use Java annotation @NotNull and @Nullable.


1 Answers

I have an app that uses a toolbar (code based on com.example.android.actionbarcompat) where I ran into the same problem. On some devices, onCreateOptionsMenu() does not get called just after onCreate() which means the action bar was not initialized, and when I tried changing an icon, the app crashed.

The good news for me was that onCreateOptionsMenu() does get called somewhat later and when it happens, I keep a reference to the menu in onCreateOptionsMenu(). I modified the rest of my activity code to check this reference before they do anything with the action bar. This still executes super fast, and the user experience is not impacted.

I suggest you do the same and defer some initialization.

like image 99
gabriel Avatar answered Oct 05 '22 08:10

gabriel