Archive

Archive for October, 2009

Android: Dialog box tutorial

October 31, 2009 24 comments

Hi folks,

There’s a lot that’s been written about the evils of dialog boxes , those little boxes that pop up to ask you how whether you’re sure you want to do action X, or provide some information Y.  The real issue with most of these dialog boxes is that they block the user interface, stealing the attention of the user, and disallowing him to deal with what he was doing until he deals with this interruption.

I agree that they are overused and often can be eliminated entirely.  For instance, instead of popping up a dialog box asking whether you’re sure you want to delete something, just do the deletion and provide a mechanism for them to undo their mistake.  Gmail does this well; the problem is that in general it’s much more difficult to provide an undo mechanism than it is to shove a dialog in the user’s face, make him choose an option he might not fully understand, and then absolve oneself of the consequences when he deletes something he didn’t intend to.

Philosophical debate aside, it’s still useful to be able to use a dialog box in a pinch.  I will step through the code to illustrate how to do so in Android.

Android provides a useful Builder implementation for Dialogs.  I’ll write a post about the niceness of the Builder design pattern eventually, but suffice to say, they allow a nice mechanism for specifying optional arguments rather than having dozens of overloaded constructors that take in various arguments.  When all the arguments have been provided to the Builder through a series of chained method invocations, you convert the Builder object into the object it creates through a final create() command.

The class used to create dialog windows is AlertDialog while the builder object I mentioned earlier is AlertDialog.Builder .  Because the dialog will be displayed on the screen, we need to provide the builder object with the Context in which it should render itself.

AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setCancelable(true);
builder.setIcon(R.drawable.dialog_question);
builder.setTitle("Title");
builder.setInverseBackgroundForced(true);
builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
  @Override
  public void onClick(DialogInterface dialog, int which) {
    dialog.dismiss();
  }
});
builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
  @Override
  public void onClick(DialogInterface dialog, int which) {
    dialog.dismiss();
  }
});
AlertDialog alert = builder.create();
alert.show();

This is the basic code you need to create a dialog box that pops up, has yes and no option, and dismisses itself gracefully when either of those two options are clicked.

Now, if you’re used to Java’s swing dialog boxes, you might be surprised to see the listeners attached to the buttons in the setNegativeButton and setPositiveButton methods  In Swing, the dialog blocks all other tasks until the dialog is dismissed; as such it’s sufficient to check the return code of the dialog, and then take action based on that value.  That doesn’t fit into the Android paradigm, however, because at all times the app must be responsive and able to be interrupted by an incoming phone call or text message, for instance.  Thus the need for the asynchronous button listeners.

This complicates things somewhat; how do I perform business logic within the context of the dialog?  How do I get handles to the object or objects on which I need to perform actions?  Unlike some other languages, one cannot pass methods as objects in Java.  As such, we should use a Function Object to the dialog, said object encapsulating the business logic that must happen when a button is pressed.

I choose to implement my function object with the Command design pattern; the source of my Command interface is as follows:

/**
 * Functor object that allows us to execute arbitrary code.
 *
 * This is used in conjunction with dialog boxes to allow us to execute any
 * actions we like when a button is pressed in a dialog box (dialog boxes
 * are no longer blocking, meaning we need to register listeners for the various
 * buttons of the dialog instead of waiting for the result)
 *
 * @author NDUNN
 *
 */
public interface Command {
	public void execute();

	public static final Command NO_OP = new Command() { public void execute() {} };
}

I provide a wrapper around the Command object in order to make the API easier to make the dialog boxes.

public static class CommandWrapper implements DialogInterface.OnClickListener {
  private Command command;
  public CommandWrapper(Command command) {
    this.command = command;
  }

  @Override
  public void onClick(DialogInterface dialog, int which) {
    dialog.dismiss();
    command.execute();
  }
}

Combining this with a variation of the earlier code, we have a general purpose method that returns a Dialog confirming whether we want to delete something.

private static final CommandWrapper DISMISS = new CommandWrapper(Command.NO_OP);

public static AlertDialog createDeletionDialog(final Context context,
    final String name, final Command deleteCommand) {

  AlertDialog.Builder builder = new AlertDialog.Builder(context);
  builder.setCancelable(true);
  builder.setIcon(R.drawable.dialog_question);
  builder.setTitle("Are you sure you want to delete \"" + name + "\"?");
  builder.setInverseBackgroundForced(true);
  builder.setPositiveButton("Yes", new CommandWrapper(deleteCommand));
  builder.setNegativeButton("No", DISMISS);
  return builder.create();
}

Here is this method used in context:

// When the delete button is clicked, a dialog pops up confirming
deleteButton.setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    Command delete = new Command() {
      public void execute() {
        deleteFromDatabase(personID);
      }
    };
    AlertDialog deletionDialog = DialogUtils.createDeletionDialog(ShowPlaceActivity.this, personName, delete);
    deletionDialog.show();
  }
});

In conclusion, you’ve seen how Android provides a nice Builder interface for creating dialogs, and how these dialogs differ from those of Java. Furthermore, you’ve seen how the Command design pattern can encapsulate business logic and be executed the instant a button is pressed.

Categories: Android, regular Tags: , ,

Google Calculator + Free text entry

October 23, 2009 Leave a comment

For those of you who don’t use Google Calculator – you should.  It’s amazing.  I use it all the time for conversions.  All you have to do is enter the units you’re converting into a google search, and one of the top results will be the Calculator interpretation of your search.

For instance, when SCPing files, I wanted to estimate how long the transfer would take for x gigabytes.  I noticed that, on average, I was getting about 11 MB/s.  I could do the conversion to GB/h by hand if I wanted to, but there’s really no reason to when Google does such a good job.

11 (MB / s) = 38.671875 GB / hour

There are hundreds of converter programs around the Internet, but what makes Google’s so powerful is that it’s smart enough to interpret your plain text queries; you don’t need to waste time selecting units from drop downs, or anything like that.  This same power of free-text entry and parsing applies to a lot of google products, especially Google maps.

Categories: regular, UI

Android – ItemizedOverlay + ArrayIndexOutOfBoundsException / NullPointerException workarounds

October 19, 2009 73 comments

There’s some gotchas to be aware of when using the ItemizedOverlay class Google provides.

Here’s the situation (a fairly common one): You have a dynamic list of Icons you wish to display on a map.  The list can grow and shrink.  You might think the following would work:

public class CustomItemizedOverlay extends ItemizedOverlay {

	private ArrayList mOverlays = new ArrayList();

	public CustomItemizedOverlay(Drawable defaultMarker, Context mContext, MapView view) {
		super(boundCenterBottom(defaultMarker));
	}

	@Override
	protected COINOverlayItem createItem(int i) {
		return mOverlays.get(i);
	}


	@Override
	public int size() {
		return mOverlays.size();
	}

	public void addOverlay(COINOverlayItem overlay) {
		mOverlays.add(overlay);
		populate();
	}


	public void clear() {
		mOverlays.clear();
		populate();
	}
}

Seems like a reasonable assumption, doesn’t it?  If you try to use this class, you will immediately see a problem – if you do not add any items to the list before it is displayed, you will get a NullPointerException.  This is well documented in two separate threads; Google has marked the issue as Won’tFix.  The workaround is to call populate() within the constructor.  OK, that’s fine.  Our new constructor looks like this:

public CustomItemizedOverlay(Drawable defaultMarker, Context mContext, MapView view) {
		super(boundCenterBottom(defaultMarker));

	        // Workaround for null pointer exception with empty list
		// <a href="http://osdir.com/ml/AndroidDevelopers/2009-08/msg01605.html">http://osdir.com/ml/AndroidDevelopers/2009-08/msg01605.html</a>
		// <a href="http://code.google.com/p/android/issues/detail?id=2035">http://code.google.com/p/android/issues/detail?id=2035</a>
		populate();
	}

You’re not out of the woods yet, though. Say you have clicked on one of the icons on the map. Now say you somehow change your list of icons, e.g. by clearing all of them off the map. Click on the same point where the icon USED to be, and you’ll be faced with a nasty stack trace:

10-19 11:02:48.009: ERROR/AndroidRuntime(1375): java.lang.ArrayIndexOutOfBoundsException
10-19 11:02:48.009: ERROR/AndroidRuntime(1375):     at com.google.android.maps.ItemizedOverlay.maskHelper(ItemizedOverlay.java:562)
10-19 11:02:48.009: ERROR/AndroidRuntime(1375):     at com.google.android.maps.ItemizedOverlay.setFocus(ItemizedOverlay.java:365)
10-19 11:02:48.009: ERROR/AndroidRuntime(1375):     at com.google.android.maps.ItemizedOverlay.focus(ItemizedOverlay.java:539)
10-19 11:02:48.009: ERROR/AndroidRuntime(1375):     at com.google.android.maps.ItemizedOverlay.onTap(ItemizedOverlay.java:455)
10-19 11:02:48.009: ERROR/AndroidRuntime(1375):     at org.mitre.coin.map.COINItemizedOverlay.onTap(COINItemizedOverlay.java:63)
10-19 11:02:48.009: ERROR/AndroidRuntime(1375):     at com.google.android.maps.OverlayBundle.onTap(OverlayBundle.java:83)
10-19 11:02:48.009: ERROR/AndroidRuntime(1375):     at com.google.android.maps.MapView$1.onSingleTapUp(MapView.java:346)
10-19 11:02:48.009: ERROR/AndroidRuntime(1375):     at android.view.GestureDetector.onTouchEvent(GestureDetector.java:503)
10-19 11:02:48.009: ERROR/AndroidRuntime(1375):     at com.google.android.maps.MapView.onTouchEvent(MapView.java:623)
10-19 11:02:48.009: ERROR/AndroidRuntime(1375):     at org.mitre.coin.view.ModifiedMapView.onTouchEvent(ModifiedMapView.java:26)
10-19 11:02:48.009: ERROR/AndroidRuntime(1375):     at android.view.View.dispatchTouchEvent(View.java:3368)

The source is closed, so you have no idea what is throwing the ArrayIndexOutOfBounds; it’s in code you didn’t write. Once again, there’s a workaround for it, but it’s certainly not intuitive, nor is it mentioned in the documentation. You must called setLastFocusedIndex(-1); whenever you structurally modify the list, but before you call populate(). The issue is documented in more detail here.

The skeleton now looks like the following:

public class CustomItemizedOverlay extends ItemizedOverlay {

	private ArrayList mOverlays = new ArrayList();

	public CustomItemizedOverlay(Drawable defaultMarker, Context mContext, MapView view) {
		super(boundCenterBottom(defaultMarker));

		// Workaround for bug that Google refuses to fix:
		// <a href="http://osdir.com/ml/AndroidDevelopers/2009-08/msg01605.html">http://osdir.com/ml/AndroidDevelopers/2009-08/msg01605.html</a>
		// <a href="http://code.google.com/p/android/issues/detail?id=2035">http://code.google.com/p/android/issues/detail?id=2035</a>
		populate();
	}

	@Override
	protected COINOverlayItem createItem(int i) {
		return mOverlays.get(i);
	}


	@Override
	public int size() {
		return mOverlays.size();
	}

	public void addOverlay(COINOverlayItem overlay) {
		mOverlays.add(overlay);
		setLastFocusedIndex(-1);
		populate();
	}


	public void clear() {
		mOverlays.clear();
		mapView.removeAllViews();
		numItems = 0;
		// Workaround for another issue with this class:
		// <a href="http://groups.google.com/group/android-developers/browse_thread/thread/38b11314e34714c3">http://groups.google.com/group/android-developers/browse_thread/thread/38b11314e34714c3</a>
		setLastFocusedIndex(-1);
		populate();
	}
}

Hopefully this pops up in Google and saves some people some trouble.

Categories: Android, Java, programming Tags:

Android – OverlayItem.setMarker(Drawable icon)

October 16, 2009 37 comments

I’ve been developing in Android for a little over two months off and on and am finding certain things I really love and certain things I hate.  This is one of the things I hate.

Android provides a nice class to manage drawing OverlayItems to a map called ItemizedOverlay; for instance you might want to display all the people in your contact list on a map.  Well, you’d probably want to display a different icon for each person, right?  OK, examine the API.

setMarker

public void setMarker(android.graphics.drawable.Drawable marker)

Sets the marker to be used when drawing this item on the map. Setting the marker to null will cause the default marker to be drawn (the marker is null by default, so you can just skip this, instead). The marker may be drawn using any combination of the null, R.attr.state_pressed, R.attr.state_selected and R.attr.state_focused attributes.

OK, that looks perfect.  Go ahead and give each OverlayItem the Drawable icon you want to use.  Look at the map and… wait a minute.  Nothing shows up.  Why is that?  It works when you haven’t specified the marker…

Well, what this method fails to inform you is that you must define the bounds of a Drawable object before you use it on a map.  The definition of Drawable says

The setBounds(Rect) method must be called to tell the Drawable where it is drawn and how large it should be. All Drawables should respect the requested size, often simply by scaling their imagery. A client can find the preferred size for some Drawables with the getIntrinsicHeight() and getIntrinsicWidth() methods.

So to solve the problem we must do the following:

Drawable icon = getResources().getDrawable(someicon);
icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
personItem.setMarker(icon);

I wish that an icon defaulted to having its drawable bounds be the rectangle (0, 0, width, height), but you must explicitly define these bounds.

Categories: Android, programming, regular Tags:

Not exactly programming related, but nerdy nonetheless.  This is a “best of” video my roommate put together of some our Super Smash Bros Brawl play.

October 11, 2009 Leave a comment
Categories: video