Question

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...

Was it helpful?

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.

OTHER TIPS

setActionView()/collapseActionView() are native functions in Android 4 ... passing null MenuItem will surely be frowned upon.

On your Android 2.3 device, the compatibility library (android.support.v4) has it's own internal implementations of setActionView()/collapseActionView() that are likely more resilient to bad input.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top