androidandroid-intentnexus-7

ActionBar menu item different behaviour


I'm working in my Android project developing with a HTC Desire (Gingerbread 2.3.7) and a Google Nexus 7 (Jelly Bean 4.3). I need to send some data from MainActivity to InfoActivity, so I use an intent. In this InfoActivity, I also have a menu item in the action bar to refresh the info.

enter image description here

In InfoActivity I show the data to the user. But this is not the problem, the problem is with the menu. Look at the following code:

public class ShowInfoActivity extends ActionBarActivity {

    private MenuItem menuItem   = null;
    // ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ...
        new OneTask().execute(...);
        // ...
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        case R.id.refresh:
            menuItem = item;
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    private class OneTask extends AsyncTask<Object, Void, String> {
        // ...
        @Override
        protected void onPreExecute() {
            MenuItemCompat.setActionView(menuItem,
                        R.layout.actionbar_indeterminate_progress);
            MenuItemCompat.expandActionView(menuItem);
        }
        // ...
        @Override
        protected void onPostExecute(String result) {
            MenuItemCompat.collapseActionView(menuItem);
            MenuItemCompat.setActionView(menuItem, null);
        }
    }

Obviously, the first time it's executed, menuItem=null, so it must crash. Incredibly, in HTC it works fine but in Nexus it obviously crashes. Why is this different between devices?

PS: I already solved it, but I want to know why this behaviour...


Solution

  • When in doubt, always check the source code. If you look at MenuItemCompat.java you'll find that it switches based on the API level like so:

        static final MenuVersionImpl IMPL;
        static {
            final int version = android.os.Build.VERSION.SDK_INT;
            if (version >= 14) {
                IMPL = new IcsMenuVersionImpl();
            } else if (version >= 11) {
                IMPL = new HoneycombMenuVersionImpl();
            } else {
                IMPL = new BaseMenuVersionImpl();
            }
        }
    

    The base setActionView method for the base implementation (which is used for 2.3 devices) just returns the MenuItem, so it wouldn't ever throw the exception:

    @Override
    public MenuItem setActionView(MenuItem item, View view) {
        return item;
    }
    

    The HoneycombMenuVersionImpl, on the other hand, delegates to another class:

            @Override
            public boolean setShowAsAction(MenuItem item, int actionEnum) {
                MenuItemCompatHoneycomb.setShowAsAction(item, actionEnum);
                return true;
            }
    

    And the delegate class attempts to call the actual method on the MenuItem, which will throw an exception:

    public static void setShowAsAction(MenuItem item, int actionEnum) {
        item.setShowAsAction(actionEnum);
    }
    

    In this particular example, checking the source code answers your question and shows you a solid strategy for dealing with compatibility across different versions of Android.