Thursday, January 30, 2014

Contextual Action Bar with Android's ExpandableListView

I have put together a sample project on Github illustrating that the Contextual Action Bar pattern can be applied to Android's ExpandableListView, if your use case implies that you either want to select groups or the childs and do not need a mixed selection.

The magic is quite simple, it mainly consists in storing the type of selection in the ActionMode.Callback's onItemCheckedStateChanged() method:

    @Override
    public void onItemCheckedStateChanged(ActionMode mode, int position,
                                          long id, boolean checked) {
      int count = lv.getCheckedItemCount();
      if (count == 1) {
        expandableListSelectionType = ExpandableListView.getPackedPositionType(
            lv.getExpandableListPosition(position));
      }
      mode.setTitle(String.valueOf(count));
      configureMenu(mode.getMenu(), count);
    }

Then you need to implement onChildClick() and onGroupClick() listeners that check the list items once action mode is active:

    @Override
    public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
      if (mActionMode != null)  {
        if (expandableListSelectionType == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
          int flatPosition = parent.getFlatListPosition(ExpandableListView.getPackedPositionForGroup(groupPosition));
          parent.setItemChecked(
              flatPosition,
              !parent.isItemChecked(flatPosition));
          return true;
        }
      }
      return false;
    }
    @Override
    public boolean onChildClick(ExpandableListView parent, View v,
        int groupPosition, int childPosition, long id) {
      if (mActionMode != null)  {
        if (expandableListSelectionType == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
          int flatPosition = parent.getFlatListPosition(
              ExpandableListView.getPackedPositionForChild(groupPosition,childPosition));
          parent.setItemChecked(
              flatPosition,
              !parent.isItemChecked(flatPosition));
        }
        return true;
      }
      return false;
    }

The sample project also illustrates a technique of defining the context actions as part of four groups, depending on two combined criteria:
  • actions for groups or children
  • bulk actions that can be applied to multiple items, and single item actions

Thursday, December 19, 2013

Supplementing Jekylls missing I18n support with Ant

Jekyll has no native support for multilingual websites, and while there exists a plugin adding that support, that does not help if your site is built on Github Pages. You could build your site locally, and push the rendered site to Github Pages, but here is an alternative solution:
In fact, if you only want to translate some strings to a couple of languages, and all translations are 100% complete, Jekyll can handle this use case easily. You put your translation data into a YAML file, e.g. named msg.yml in the _data directory

en:
  hello_world: Hello world
de:
  hello_world: Hallo Welt
fr:
  hello_world: Bonjour le monde
 
Then the following lines in your template, will assign the array for the page language to an array named msg:

{% capture pagelang %}{% if page.lang %}{{page.lang}}{% else %}en{% endif %}{% endcapture %}
{% assign msg = site.data.msg[pagelang]  %}
 
in your page's front matter, you simply declare the lang, and make use of the msg array:

---
layout: neu
lang: en
---
{{msg.hello_world}}

This solution fails if your translations are partial, and you want to fall back to the default language, if a certain string is missing, unless you programm this fallback into  all your references to the msg array:

{% if msg.hello_world %}{{msg.hello_world}}{% else %}{{site.data.msg.en.hello_world}}{% endif %}

Since this would make your content difficult to read and maintain, I came up with the following idea of preprocessing the content.

My content now resides in a file content.html.tmpl, where I use a special marker for localized content:

{{t.hello_world}}
 
and I let a ant task copy the template and replace this marker with the full-blown jekyll syntax for falling back to the default locale:

<project default="localize" name="MyWebsite">
  <target name="copy">
    <copy encoding="utf-8" file="content.html.tmpl" overwrite="true" tofile="content.html"/>
  </target>
  <target depends="copy" name="localize">
    <replaceregexp
        byline="true"
        match="\{\{t\.(.*?)\}\}"
        replace="{% if msg.\1 %}{{msg.\1}}{% else %}{{site.data.msg.en.\1}}{% endif %}">
      <fileset dir=".">
        <include name="content.html"/>
      </fileset>
    </replaceregexp>
  </target>
</project>

Put this xml into a file called build.xml, and then simply invoking ant will execute the task. This task currently only handles as an example one single file, but ant could easily handle applying the same transformation to a whole fileset.

Sunday, November 24, 2013

Android Calendar Provider Instances Query on Legacy systems

I have run into a strange problem, when querying the Instances URI of Androids Calendar Provider on older Android versions. I was trying to get event instances from a given calendar (with id and planerCalendardId) and restrict to instances whose begin date is between two values, but the following query stubbornly refused to return any results:

    String[] INSTANCE_PROJECTION = new String[] {
          Instances.EVENT_ID,
          Instances._ID
        };
      Uri.Builder eventsUriBuilder = CalendarContractCompat.Instances.CONTENT_URI
          .buildUpon();
      ContentUris.appendId(eventsUriBuilder, lastExecutionTimeStamp);
      ContentUris.appendId(eventsUriBuilder, now);
      Uri eventsUri = eventsUriBuilder.build();
      //Instances.Content_URI returns events that fall totally or partially in a given range
      //we additionally select only instances where the begin is inside the range
      //because we want to deal with each instance only once
      Cursor cursor = getContentResolver().query(eventsUri, INSTANCE_PROJECTION,
          Events.CALENDAR_ID + " = ? AND "+ Instances.BEGIN + " BETWEEN ? AND ?",
          new String[]{
            planerCalendarId,
            String.valueOf(lastExecutionTimeStamp),
            String.valueOf(now)},
            null);

After banging my head for some time in different directions, I found out that only inspecting the source makes the behaviour understandable. Older versions of the Calendar provider, incorrectly handle the selection arguments passed in, in fact they throw them away. As can be seen in http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android-apps/2.2_r1.1/com/android/providers/calendar/CalendarProvider2.java on lines 660,661, the selectionArgs parameter to the query method is simply not passed to handleInstanceQuery.
The problem can be worked around by merging the selection arguments into the selection.

      Cursor cursor = getContentResolver().query(eventsUri, INSTANCE_PROJECTION,
          Events.CALENDAR_ID + " = " + planerCalendarId + " AND "+ Instances.BEGIN +
              " BETWEEN " + lastExecutionTimeStamp + " AND " + now,
          null,
          null);

Saturday, November 9, 2013

Open bitcoin URLs in firefox with Multibit (Ubuntu 12.10 with Gnome-Shell)

Having created Bitcoin donation URLS for my OpenSource projects MyExpenses and SendFithFtp, I tried to find out how to make Firefox open the bitcoin URLs with Multibit.
I am running Ubuntu 12.10. with Gnome Shell, and after some experiments, the following worked:
Multibit creates a menu entry at ~/.local/share/applications/MultiBit\ 0.5.14-1382603328396.desktop

Modify the file to make it handle URLs:

[Desktop Entry]
Categories=
Comment=MultiBit 0.5.14
Comment[de]=MultiBit 0.5.14
Encoding=UTF-8
Exec=/usr/bin/java -jar /home/michael/MultiBit-0.5.14/multibit-exe.jar %u
GenericName=
GenericName[de]=
Icon=/home/michael/MultiBit-0.5.14/multibit48.png
MimeType=x-scheme-handler/bitcoin
Name=MultiBit 0.5.14
Name[de]=MultiBit 0.5.14
Path=/home/michael/MultiBit-0.5.14
ServiceTypes=
SwallowExec=
SwallowTitle=
Terminal=
TerminalOptions=
Type=Application
URL=
X-KDE-SubstituteUID=false
X-KDE-Username=root

and register it with:

xdg-desktop-menu install --novendor ~/.local/share/applications/MultiBit\ 0.5.14-1382603328396.desktop

When clicking in Firefox on a link to a Bitcoin URL, and you get prompted for selecting an application to open the URL with, select /usr/open/xdg-open .

Multibit unfortunatley only handles the URL correctly, when it is already running.

Saturday, July 6, 2013

DialogFragment displaying options from a cursor held by the Activity

Continuing to chase down performance problems with Android's StrictMode in My Expenses: On several occasions, I needed to present the user a dialog for selecting an item from a cursor. Previously I was loading this cursor in the fragment without passing through a CursorLoader. Instead of loading the cursor again, if it is already held in the activity, I was looking for a way to access the activity's cursor, and came up with SelectFromCursorDialogFragment. It defines an interface (SelectFromCursorDialogListener) that the activity displaying the dialog implements in order to 1) provide the cursor (getCursor) and 2) to process the result (onItemSelected).

An example use case is the "Move transaction" command, that allows to move a transaction from one account to another, and needs to present a dialog for selecting the target account. Following code is used for displaying the dialog

      args = new Bundle();
      args.putInt("id", R.id.MOVE_TRANSACTION_COMMAND);
      args.putString("dialogTitle",getString(R.string.dialog_title_select_account));
      args.putString("column", KEY_LABEL);
      args.putLong("contextTransactionId",info.id);
      args.putInt("cursorId", ACCOUNTS_OTHER_CURSOR);
      SelectFromCursorDialogFragment.newInstance(args)
        .show(getSupportFragmentManager(), "SELECT_ACCOUNT");

SelectFromCursorDialogFragment receives a bundle with the following required keys
id
Allows to identify the context from which the dialog is opened in the onItemSelected callback.
dialogTitle
The title for the dialog.
column
the column being used for the options to be selected.
cursorId
the id under which the cursor will be made available in getCursor.
 
The bundle is handed back in the callback, so any additional information can be added that is needed for processing, in this example we store as "contextTransactionId", the transaction that the user wants to move.
In this example, we implement the interface in the following way

  @Override
  public void onItemSelected(Bundle args) {
    switch(args.getInt("id")) {
    case R.id.MOVE_TRANSACTION_COMMAND:
      Transaction.move(
          args.getLong("contextTransactionId"),
          args.getLong("result"));
      break;
    }
  }
  @Override
  public Cursor getCursor(int cursorId) {
    switch(cursorId) {
    case ACCOUNTS_OTHER_CURSOR:
      return new AllButOneCursorWrapper(mAccountsCursor,currentPosition);
    }
    return null;
  }

The "result" key in the bundle is where the selected item's id is stored.
The cursor returned for the id ACCOUNTS_OTHER_CURSOR is the solution to another challenge. The activity has a cursor for all accounts, but the target obviously needs to be a different account from the current one, so we need to exclude one row from the cursor, which is handled by the AllButOneCursorWrapper class.

EDIT: It turned out, that above solution only worked as long, as the user does not change orientation while displaying the dialog. To make the dialog react correctly to orientation changes proofed to be quite complicated. The problem is, that when the activity is recreated and the fragment instantiated, the cursor is not yet available, and you need to hook into the cursor loader's callback in order to call up the fragment and set the cursor there. SelectFromCursorDialogFragment now implements this requirement through the following changes:
  1. Instead of using the builder's setSingleChoiceItems method, an SimpleCursorAdapter is created and stored as class member variable.
  2. SelectFromCursorDialogFragment defines a setCursor method which uses the adapter's swapCursor method.
  3. the getCursor method of the SelectFromCursorDialogListener interface has a second argument tag which allows the activity to store the tag of the fragment, if it does not have the cursor the fragment needs.
  4. in the activity's onLoadFinished method, we check if there is fragment tag stored as a callback, retrieve the fragment, and use its  setCursor method.
Some overhead for a tiny detail, but worthwhile, if you pay attention, as I try to do, to have your app handle orientation changes correctly.

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.

Sunday, December 9, 2012

Moving from jquery tabs and iframes to jekyll

For the MyExpenses web site, I had used Jquery Tabs for navigating between the different sections, and contents that were also displayed from inside the app, like the user manual and the news were displayed in iframes. That worked well, but needed considerable tweaking through Javascript for
  • being able to link to individual sections and their subpages
  • resizing the iframe dynamically
It worked well, until I wanted to integrate Disqus commenting to the News which did not fit into the iframe where the News were displayed.
While looking for a solution, I discovered that Github pages, where the website is hosted, supports Jekyll, which is a mechanism for generating static pages from templates.
With the Jekyll, the navigation is now stored in one template, that is considerably simpler and easier to maintain than the Javascript code used before, and each time I push content to Github, the site is regenerated from this template. Manual and news are still generated from Docbook through XSLT stylesheets, but those output now the YAML header that Jekyll interprets.
The new interface now uses vertical tabs (the design needs to be polished, I admit), that make the whole site display better on the smartphone, and I no longer need a special navigation-less in-app display.
The added advantage: MyExpenses now can call Android's browser for displaying content and no longer relies on its own Webview, thus no longer needs the permission to access the Internet, which understandably is a stumbling block for security sensitive users when dealing with a financial application.