Archive

Archive for the ‘Android’ Category

Of Rubber Stamps and CheckBoxes: Why your Android ListView is broken

November 5, 2010 5 comments
There are a lot of tutorials on how to use ListViews, the ubiquitous UI element in which a list of information is presented in a linear, scrolling view.  However, I do not think they do enough at explaining just why they behave in the way they do, especially with respect to stateful UI components. 

An example list view, with a very simple "view" for each row

The problem is, people do not quite understand what it means for views to be reused in the context of a ListView. This blog post is intended to explain what that means, and what consequences that has for your list views and your adapters.

The following description comes from the Swing documentation on how JTables are rendered, but it is instructive for this description, and introduces the central metaphor that helps explain what’s going on with the way ListViews are rendered.

Before you go on to the next few tasks, you need to understand how tables draw their cells. You might expect each cell in a table to be a component. However, for performance reasons, Swing tables are implemented differently.

Instead, a single cell renderer is generally used to draw all of the cells that contain the same type of dataYou can think of the renderer as a configurable ink stamp that the table uses to stamp appropriately formatted data onto each cell.

Why would the designers implement the rendering this way?  Imagine if each row of the table (or list) were a separate object.  Each row would hold references to its own UI widgets, e.g. for TextViews (labels), ImageViews (images).  If there were N rows and each row takes on average M bytes of memory, you’d have an average memory footprint of N*M bytes.  Given that these lists need to be able to display a large number of items, this would be an unacceptable memory footprint.  Furthermore, since most of the list would be offscreen, most of the memory would be completely wasted.  While it would be simpler to implement, it would bring any Android phone to its virtual knees.

The designers, borrowing from the Swing designers, chose instead a much more memory efficient way of rendering the lists – if only X items are visible on the screen, where X is much less than N, the total memory footprint would be X*M bytes.  It takes more work in the ListView class to calculate exactly which rows are visible and to ensure that only their views are rendered, but it’s worthwhile.  Each visible row uses its own “rubber stamp”, filling in the details specific to that row. 

What you do by defining your adapter view in XML is define the raw features that make up your rubber stamp.

This is the basic rubber stamp that might be used for displaying tweets in a Twitter application.  There are four elements: an image, a username label, a paragraph text label, and another label for how the tweet was posted.
Let’s say that at most four tweets can fit on the screen at once.  When the first row needs to be rendered, the view is null (hasn’t been instantiated) and must be inflated from XML.  This process is repeated for the second and third row.  When the next tweet needs to be loaded on the screen, it can reuse the view (rubber stamp) of the view that’s just left the screen at the top.

Row views are reused

This leads us into what trips a lot of Android developers up.  You cannot maintain state within the UI components alone.  What do I mean?  You cannot use, for instance, Checkboxes in each row and hope to have them keep track of which items are checked or not.  Why?  This will work for a small number of rows, but once you get past the number that can fit on one screen, you’ll start to see weird problems.  If you can fit X rows on the screen, then item 1, X+1, 2X+1, … will all share the same checkbox.  Thus by changing the state of item 1, you will change the status of all the rows further down the line that are a multiple of X away from it.  The correct way of handling this is to have each row data object, that which is being visualized by the Adapter, maintain this state.  You can see an example of this in a tutorial on how to use checkboxes in listviews. I see this question come up repeatedly on StackOverflow, and it stems from the programmer not understanding how these views are reused in each row.
The metaphor of the rubber stamp fits more with that of JTable rendering, where there truly is a single component that gets reused over and over again, whereas there are multiple reused row View objects in the case of Android.  Why isn’t the single component approach used for Android?  At first I assumed it was, until I read this great explanation on android.amberfog which illustrates how there are actually as many views inflated as there are potential rows visible at any time.  The reason as far as I can imagine is that the user needs to be able to interact with each rows.  If all that was required was the static view of each row, then it would be fine to use a single rubber stamp, render a row, change an offset in the graphics context, and render the next row.  As it stands, however, each row needs to be able to detect when it is clicked on, long clicked on, etc.  Furthermore, each row could have UI elements which themselves could be interactive.  Thus it is insufficient to present static views of the data; the user would be unable to interact with the rows in the way he expects.
I hope this explanation is helpful, and if I’ve gotten anything wrong, I’d love to hear about it in the comments.

(All images were created with the use of Balsamiq, a great tool for mockups).

Advertisements

Android – disappearing emulator ? Restart adb server

September 13, 2010 12 comments
While developing for the Android platform has gotten a lot better in the past year, there are still some rough edges.  In particular, emulators sometimes disappear from the list of attached devices (accessed via adb devices from the terminal, or within the Devices tab when in DDMS view in the Eclipse plugin).  When this happens, you cannot interact with it via the terminal or Eclipse.  That’s a pretty big problem.


A screen shot illustrating a running emulator that does not appear in the list of attached devices

An emulator within the devices window

To solve this, you should take the following steps:


# Device is running but not showing up
[497][nicholasdunn: /Users/nicholasdunn]$ adb devices
List of devices attached

# Kill and restart
[498][nicholasdunn: /Users/nicholasdunn]$ adb kill-server
[499][nicholasdunn: /Users/nicholasdunn]$ adb start-server
* daemon not running. starting it now *
* daemon started successfully *

# Device appears, but is listed as offline
[500][nicholasdunn: /Users/nicholasdunn]$ adb devices
List of devices attached
emulator-5554    offline

# One more invocation of adb devices should get it recognized
[501][nicholasdunn: /Users/nicholasdunn]$ adb devices
List of devices attached
emulator-5554    device

If this happens to you frequently (it does to me), you can create an alias within your .bash_profile file (~/.bash_profile):

alias adb-restart='adb kill-server; adb start-server; adb devices; adb devices'

Reload your .bash_profile file:

source ~/.bash_profile

You can then invoke it from the terminal by typing adb-restart.  Sometimes one invocation of adb devices is enough to have the emulator show up as a device; others requires two.  Not sure why that is.  To be safe I’m including two in the script.

Eclipse + Android + SVN = fail

July 28, 2010 14 comments

If you are developing a project in SVN (I’m sorry for your predicament) and using Eclipse, please heed my advice.  Otherwise you could unintentionally delete your source directory during a routine cleanup.

Now, how could you possibly do that? you might wonder.  Are you incompetent?  Well, maybe, but not in this case.  The situation was this: I was about to make a commit of some changes to the code and did a svn status before committing to ensure that everything was as I wanted it to be.  I noticed that there were dozens of files in the bin directory that either had question marks next to them (.class files) or showed up with exclamation points next to them (indicating that SVN thought they’d gone missing).  Given that the binary class files do not belong in version control, I went ahead and did a svn rm -rf bin (recursively delete all the files under bin).  I went ahead and finished the commit.  Later I went to make another commit and I got an error telling me that the src folder didn’t exist in the svn repository.  Horrified, I my unix history and then the svn commit log:

529  svn rm –force bin/
530  svn ci -m “Bin should not be under version control.”

Revision 249
Author:     ndunn
Date:   Mon Jul 26 14:52:14 2010 UTC (62 minutes, 34 seconds ago)
Log Message:

Bin should not be under version control.

trunk/projname/src/     deleted

Um.  What?  I’m pretty sure bin != src.  Well.  You’d think that.  But if you’re using Eclipse and haven’t dived in four menus deep, you’d be wrong.

You see, for some reason Eclipse copies all the files from the src folder into the bin folder when you build the project.  Why it does that, I’m not sure.  I’d love to know why.  Regardless of the reason, all of the files in the src are copied.  Including… the hidden .svn folders.  These folders are how subversion keeps track of the changes to your files.  The problem is, since the .svn folders were copied, and bin was deleted, it was exactly the same to svn as if I had deleted src.  Which is why my src folder disappeared.

How do you work around this?  Well, I mentioned earlier that by default eclipse copies all of the files from src to bin.  You can add filters so that certain files/extensions are ignored, if you know where to look.  In Eclipse, go to Preferences -> Java -> Building -> Output Folder and make sure that *.svn is on the line for filtered resources.  It will prompt you to rebuild your project.  Once it does so, you will not have this potential mistake looming over your head.

Finally, here’s the svn command you need to revert a folder if you accidentally delete it and check the deletion into source control.  Let’s assume you deleted the folder in revision n.  Then you want to fetch the status of that folder at n – 1

n = 249

svn  copy -r248 -m "Restore deleted trunk/src"  svn+ssh://username@company/path/to/trunk/src@248  svn+ssh://username@company/path/to/trunk/src

Thanks to the android developers group for helping me figure out what was going on; see here for details.

I have submitted a bug / feature enhancement request to the Eclipse developers so that the default behavior is a bit more sane.  You can find that bug report here. The commenters mention that installing the svn plugin for Eclipse fixes the problem, but I don’t think I or anyone else should have to install something just to have sensible behavior.

The StackOverflow post giving a shorter synopsis of the problem can be found here.

Conclusion:

Lots of Android developers use Eclipse to develop their projects due to the plugin support available.  I’d wager a fair number of them use SVN as well for their source control.  They should be very wary about the default behavior of Eclipse, and ensure that they filter to remove .svn files from being copied over to their src folder.  Take it from me – it’s not a good feeling seeing a commit message with your name on it deleting the src folder.  Or you could use git and not have to worry about any of these problems…

Categories: Android, eclipse, Java, svn

Android: Principle of least surprise violation in GPS emulation

July 16, 2010 1 comment

Some of the most viewed posts on this blog have been about Android development, namely some workarounds for bizarre bugs that Google *still* has not fixed, months later.  I haven’t touched Android in a few months, but recently have started to dive back in.  Here’s yet another instance where an Android related piece of software performs completely counter-intuitively.

When developing an Android application, I often use the emulator rather than an actual hardware device.  In order to test GPS related functionality, you can either send simulated GPS events through the Dalvik Debug Monitor Service (DDMS) via Eclipse or through the Android terminal via telnet.  I’ll detail surprising bugs/defects in both ways.

DDMS

The DDMS Eclipse view is available after installing the ADT plugin, and it’s absolutely crucial in order to see logging messages produced from your application (“LogCat”).    It also allows you to send GPS coordinates, as the following screenshot shows.

Unfortunately there is a bug in this software that drove me up the wall, and I never would have figured it out myself.  I was running into the problem where the first GPS coordinate sent would be received by the emulator, but all subsequent ones would not.  It turns out the problem has to do with locales and decimals being treated as commas, or vice versa.  See the following Google bug report for more details.  The bug has been open over a year; fortunately it looks like it’s slated to be fixed in the next SDK release.

Telnet

If you telnet into your emulator instance, you have access to the Android console environment.

telnet localhost 5554

Android Console: type ‘help’ for a list of commands
OK

One of the commands you can send is fix, which send a latitude longitude pair to the emulator.  Well, that’s what I assumed it did.  After wondering why all my locations were were coming out incorrectly, I read the documentation a little more carefully.

fix <longitude> <latitude> [<altitude>] Send a simple GPS fix to the emulator instance. Specify longitude and latitude in decimal degrees. Specify altitude in meters.

There is a VERY strong convention to specify latitude before longitude.  (“latitude and longitude” returns 4,240,000 results as opposed to 542,000 for “longitude and latitude”)  Having this command take arguments in the opposite order is very surprising and counter-intuitive.   I notice that the DDMS screen has it longitude, latitude as well, but even within the Android APIs, you specify the latitude first!

I realize I should have read the documentation first, but it still is a flaw that the software behaves in unexpected manners (it violates the Principle of Least Surprise/Astonishment), and inconsistent with how API calls in the rest of the OS work.  If someone has an explanation for why it’s written this way, I’d love to hear it.

Edit: Later I learned that KML has the same convention, longitude followed by latitude. This corresponds with the x,y ordering conventional in math, so it’s perhaps not so surprising after all. This ordering problem is fairly minor in the grand scheme of things, and reflects more on my own inexperience with geospatial APIs than any fault of Android itself.

Android: Dialog box tutorial

October 31, 2009 23 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: , ,

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: