Saturday, January 18, 2014

Android ShareActionProvider does not provide share action without shareHistoryFileName set

TL;DR - It is compulsory to call setShareHistoryFileName() after initialising from getActionProvider().


I was trying to call the ShareActionProvider from a Contextual Action Bar. As it turned out, after trying to figure out why it didn't work on my Jelly Bean emulator, I found this issue that has had hardly any attention on it. Running the built-in ApiDemo (for API 10, on both the emulator and my actual device) proved that it really doesn't have anything catered for this action. Bottomline is, I had to switch to plugging in my own phone to test my change for this bit.

When the documentation mentioned "Defaults to DEFAULT_SHARE_HISTORY_FILE_NAME." my assumption was that I did not need to even call setShareHistoryFileName() since it's already set with that as default. Why shouldn't it? And without which, my share action fails to react, even when the icon is sitting nicely on the screen.

I only managed to find people asking how to disable the share history, but not this other bit. I don't really care for the share history in this case, but getting it to work without which seems impossible. Dear people at Google, maybe would you like to consider either stating that setShareHistoryFileName() must be called, to pass in the DEFAULT_SHARE_HISTORY_FILE_NAME, or maybe just plain set it in by default or to null if it was never set? Isn't that what built-in defaults are for?

Even though I know there are tons of codes floating around that's basically similar every which way, here's a snippet of my codes for posterity:
private ActionMode.Callback actionModeCallBack = new ActionMode.Callback() { ...
        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            MenuInflater inflater = mode.getMenuInflater();
            inflater.inflate(R.menu.confirmed_context_menu, menu);
           
            shareAction = (ShareActionProvider) menu.findItem(R.id.share_confirmed).getActionProvider();
            shareAction.setShareHistoryFileName(ShareActionProvider.DEFAULT_SHARE_HISTORY_FILE_NAME);
            shareAction.setShareIntent(createShareIntent());
            return true;
        }
 I must qualify my lack of understanding was specifically for this bit of use for the ShareActionProvider in a contextual action bar that appears only after a long-press, but no where did I spot anybody mentioning that they succeeded in calling the Provider without setting the share history... or is everyone just copying the codes from documentation line-for-line?

Friday, January 17, 2014

Closing the Contextual Action Bar when switching to another Fragment

Been playing around in Android in my spare time when I encountered this... I have 3 Fragments listed in tab mode on the action bar:
  1. Fragment #3 is displayed, and has a contextual action bar shown from a long-press.
  2. Selecting Fragment #2 brings me to the page, but with the contextual action bar left over from Fragment #3.
 Clearly at this stage, I'd want the contextual action bar gone, but the results from Google were inconclusive. Fortunately, a bit of random checking in the documentation turned up onDestroy() in the Fragment class. There were hints and speculation suggesting the use of hide()/finish()/invalidate() and possibly applying them in the main Activity, from StackOverflow but I didn't find them exactly useful to my situation.

By chance, I decided to give one combination a shot, gave it a whirl and it seems to be working for me. It's surprisingly straightforward.

Here's my actionMode and its callBack:
    private ActionMode actionMode = null;
    private ActionMode.Callback actionModeCallBack = new ActionMode.Callback() {
       
        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            return false;
        }
       
        @Override
        public void onDestroyActionMode(ActionMode mode) {
            try {
                for(int i=0;i<getListView().getCount();i++) {
                    getListView().setItemChecked(i, false);
                }
                updateHighlightsOnChecked();
                getListView().setChoiceMode(ListView.CHOICE_MODE_NONE);
            } catch(IllegalStateException e) {
                Log.d(app_name_tag, "ConfirmedFragment.actionModeCallBack.onDestroyActionMode IllegalStateException");
            }
            mode = null;
        }
       
        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            MenuInflater inflater = mode.getMenuInflater();
            inflater.inflate(R.menu.confirmed_context_menu, menu);
            return true;
        }
       
        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            List<Integer> itemPositionList = new ArrayList<Integer>();
            Log.d(app_name_tag, "checked items: " + getListView().getCheckedItemCount());
            SparseBooleanArray bArr = getListView().getCheckedItemPositions();
            if(bArr != null) {
                for(int i=0;i<getListView().getCount();i++) {
                    if(bArr.get(i)) {
                        itemPositionList.add(i);
                    }
                }
            }
           
            switch(item.getItemId()) {
                case R.id.remove_confirmed:
                    callback.onConfirmedOrderRemoved(itemPositionList);
                    updateOrderList();
                    mode.finish();
                    return true;
            }
           
            return false;
        }
    };

The actionMode gets initiated in the long-click listener:
@Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
       
        getListView().setOnItemLongClickListener(new OnItemLongClickListener() {
           
            @Override
            public boolean onItemLongClick(AdapterView<?> adapterView, View view, int position, long id) {
                getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
                getListView().setItemChecked(position, true);
                updateHighlightsOnChecked();
                actionMode = getActivity().startActionMode(actionModeCallBack);
                view.setSelected(true);
                return true;
            }
        });
    }

And lastly, the most important part:

    @Override
    public void onDestroy() {
        super.onDestroy();
        if(actionMode != null)
            actionMode.finish();
    }

I figured since the contextual action bar (or pretty much everything else) would be wiped when the fragment gets swapped out, it can give the actionMode a holler to finish its business.

Give it a try and let me know if it works out for you.