Thursday, July 4, 2013

Configuring Actionbar from a Fragment using CursorLoader inside a ViewPager

My Expenses allows to swipe through accounts using a ViewPager.  with a FragmentPagerAdapter, i.e, each list of transactions has its own fragment.
The actionbar has a "Reset" menu item, that should be disabled/invisible in case the account whose page is visible has no transactions. Until now, this was done in the activity's onPrepareOptionsMenu method by a database call that retrieved the row count for the current account. Not surprisingly, this call got marked as performance impeding, when I evaluated the app's performance with StrictMode.

I thought it would be easy to have the fragment take care of configuring the menu item, since it has the cursor, and knows if it has any transactions to display. Nevertheless, it took me some time to find out the working solution. First, you need to take care of the fact that the ViewPager instantiates the fragments before they become visible, so you need a mechanism, to trigger the reconfiguration when the fragment becomes visible.
I was misled by the assumption that I would have to either overwrite or use the fragment's setMenuVisibility method, but in fact this method is already called by the adapter with its argument menuVisible as true, when the page becomes visible, and it takes care of invalidating the options menu.

Having understood this, the solution becomes quite simple and can be seen in this commit.

First in the cursor loaders callback, we store in a boolean field hasItems the information if the cursor contains any rows. And if we are dealing with the fragment that is currently visible, we invalidate the options menu. This second step takes care of situations where the cursor is loaded again, when a transaction is added or deleted. I am using the methods provided by ActionBarSherlock, but the solution should work the same with the equivalent standard Android API methods.

   public void onLoadFinished(Loader arg0, Cursor c) {
     mAdapter.swapCursor(c);
     hasItems = c.getCount()>0;
     if (isVisible())
       getSherlockActivity().supportInvalidateOptionsMenu();
   }
 
   @Override
   public void onLoaderReset(Loader arg0) {
     mAdapter.swapCursor(null);
     hasItems = false;
     if (isVisible())
       getSherlockActivity().supportInvalidateOptionsMenu();
   }
 
Second, we use hasItems field in onPrepareOptionsMenu, which now is triggered in both situations, we are interested in: first when the visibility of a fragment changes, second when the cursor for the visible fragment is reloaded. 

  @Override
  public void onPrepareOptionsMenu(Menu menu) {
    if (isVisible())
      menu.findItem(R.id.RESET_ACCOUNT_COMMAND).setVisible(hasItems);
  } 

Finally, we need to call

      setHasOptionsMenu(true); 

in onCreate() in order to make sure that onPrepareOptionsMenu is called on our fragment. It does not matter that the menu item is not added by the fragment, but by the activity. The fragment is allowed to manipulate the menu created by the activity.

No comments:

Post a Comment