Monday, March 29, 2010

Avoiding ConcurrentModificationException in MapView.draw()

Throwing this out there because it may help someone. For whatever reason, you may have gotten the idea that you would like to add and remove Overlays to your MapView as you see fit. Problem is, that MapView may be iterating through that List when you pull the rug out from under it and remove something from that List. Adding is okay of course.

Here's my advice, don't ever remove from it. Just render it ineffective. You may find it best to just empty the collection that backs your ItemizedOverlay. Or override draw() and add a hook to return early without doing anything in a particular circumstance. Of course, to do any of this you'll need to maintain a handy reference to the Overlay you add.

So remember, it's okay to call mapView.getOverlays.add() at any time, but don't call mapView.getOverlays.remove(), because if the timing is off, it will blow up on you.

While I'm writing this, I was thinking, you may be able to call remove(), by first disabling your MapView. That's worth a shot if it works in your scenario.

ItemizedOverlay and ArrayIndexOutOfBoundsException

If you're working with Google's Maps API on Android, you've probably encountered an ArrayIndexOutofBoundsException in ItemizedOverlay. To recap, ItemizedOverlay is a useful implementation of Overlay that displays a set of items on the map. More often than not, your data is dynamic, and this set of items will grow or shrink over time. It may even be an empty set.

The ArrayIndexOutOfBounds exception is usually encountered because you've done one or more of the following:
  • Overlayitem getItem(int position) returns null in your ItemizedOverlay
  • The set of items is empty (related to above).
  • You're concurrently modifying your backing Collection of OverlayItems while the draw() method is iterating through them.
Let's quickly look at a solution for each one of these. The first is for you to solve. Why is it returning null? Debug your code.

I've read some crazy ideas about how to solve the 2nd problem, your backing collection is empty, and ItemizedOverlay pukes. This is very easy to workaround. Override the draw() methods, and return early. Here's an example:


public boolean draw(Canvas canvas, MapView mapView, boolean shadow,long when) {
if (myBackingList.isEmpty()) {
return false; // False means there's no need to immediately redraw.
}
}

Finally, it's important to know that when ItemizedOverlay is executing the draw() methods, it's actually looping through your Collection of OverlayItems by calling getItem() and size(). During this time it would be unwise to modify the collection. Your first instinct is probably to use a synchronized collection or call Collections.synchronizedList (or whichever is appropriate). But this probably won't help because ItemizedOverlay isn't calling the iterator on your collection, it doesn't know what your collection is. Instead, it's calling getItem(), N times.

The approach that works is to synchronize access to your collection in getItem(), size(), and and your call to super.draw(). Additionally, you'll need to synchronize wherever in your code you modify the collection.

So to wrap up, make sure you override draw() to handle the empty set scenario and synchronize access to your backing collection.

Good luck!

Wednesday, March 10, 2010

R-Line App without Ads

Just to prove that I'm not a full-on ad-whore, I've removed the ads from the R-Line. After all, it is for my home town. Enjoy!