Android – ItemizedOverlay + ArrayIndexOutOfBoundsException / NullPointerException workarounds
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.
Damn. Thank you.
This helped me a ton! Thanks a bunch for these two workarounds.
Thanks, your a star!
Nice one 😉 many thanks
Ok man, thanks a lot!!
Thanks a lot man !!! I spent so much time without find this damn problem >.<;
Wow… I can’t believe this is documented better. Nice job! Solved all of my problems! I’m sending links around to this now.
Thanks! You saved my day (and a lot of troubles i had the last weeks)
Thank you SO MUCH!
… very obscure problem…. thanks a lot for this write up / solution!!
Haha, it may be obscure but this is far and away the most commented article on this blog. Looks like they still haven’t fixed this problem.. oy.
Thank you!
Say that you want to only remove one item from your list, how do you do that? I tried with mOverlays.remove(index) but keep getting the same exception, not on all items but some of them.
As far as I can see, that works as long as you are updating the list from the UI thread but if you have another thread updating the list, you will run back into the ArrayIndexOutOfBoundsExeception. As an ugly workaround, I synchronized adding elements to list, size() and get methods. The downside is that this will freeze the map for a second if there are lots of items to add.
Thanks! You are helping a lot of people. Damn Google documentation sucks. This post is the ONLY solution to the problem.
Glad it was helpful – if you find anyone running into this problem, send them on my way
My solution to update from a background thread is to use an AsyncTask to fetch the data in background and then update the map in the UI thread using AsyncTask’s onProgressUpdate().
Hi,
I have the same problem but your solution doesn’t work for me;
I add an overlay every time that an user tap on the screen and I would like that an user can be able to delete an overlay by tapping on it but every time I get an error, and i don’t know how to make it work.
Post your code (or link to it) and I’ll try to help you out.
this are the two implementation of onTap() that I’m using:
private class PoiOverlay extends ItemizedOverlay{
private ArrayList mOverlays = new ArrayList();
private Context context;
private int mSize;
public PoiOverlay(Context context) {
super(null);
this.context=context;
this.mSize=mOverlays.size();
populate();
// TODO Auto-generated constructor stub
}
@Override
protected boolean onTap(int index) {
int id=mOverlays.get(index).id;
mOverlays.remove(index);
return true;
}
@Override
public boolean onTap(GeoPoint p, MapView mapView) {
// TODO Auto-generated method stub
if (super.onTap(p,mapView))
return true;
createOverlay(p); //here i create and add a new overlay
return true;
}
public void addOverlay(MyOverlayItem overlay) {
Drawable drawable=getMarker(overlay.markerInt);
overlay.marker=drawable;
mOverlays.add(overlay);
setLastFocusedIndex(-1);
populate();
}
@Override
protected MyOverlayItem createItem(int i) {
// TODO Auto-generated method stub
Log.d(“inserito”,”:”+i);
return (mOverlays.get(i));
}
@Override
public int size() {
// TODO Auto-generated method stub
return mOverlays.size();
}
@Override
public void draw(Canvas canvas, MapView mapView, boolean shadow) {
// TODO Auto-generated method stub
super.draw(canvas, mapView, shadow);
}
private Drawable getMarker(int resource) {
Drawable marker=getResources().getDrawable(resource);
marker.setBounds(0, 0, marker.getIntrinsicWidth(),marker.getIntrinsicHeight());
boundCenterBottom(marker);
return(marker);
}
public void clear() {
mOverlays.clear();
//mapView.removeAllViews();
setLastFocusedIndex(-1);
populate();
}
}
class MyOverlayItem extends OverlayItem{
public int id;
public int type;
Drawable marker;
public int markerInt;
public MyOverlayItem(GeoPoint point, String title, String snippet, int id, int markerInt) {
super(point, title, snippet);
this.markerInt=markerInt;
this.id=id;
// TODO Auto-generated constructor stub
}
@Override
public Drawable getMarker(int stateBitset) {
setState(marker, stateBitset);
return marker;
}
}
I looked through the code and made some comments here: http://pastebin.com/KQK8XaQm
Let me know if that helps. Otherwise I’d need some more information to go off of, namely the stack traces, or what’s going wrong.
Thanks for your reply, but I already did that:
with createOverly I call a method in my MapActivity class that create an overlay calling addOverlay on my itemizedOverlay istance.. but it doesn’t work again…
“I add an overlay every time that an user tap on the screen and I would like that an user can be able to delete an overlay by tapping on it but every time I get an error, and i don’t know how to make it work.”
what’s the error, please post a stack trace
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): FATAL EXCEPTION: main
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): java.lang.IndexOutOfBoundsException: Invalid index 1, size is 1
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:257)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at java.util.ArrayList.get(ArrayList.java:311)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at com.google.android.maps.ItemizedOverlay.getItem(ItemizedOverlay.java:419)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at com.google.android.maps.ItemizedOverlay.focus(ItemizedOverlay.java:538)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at com.google.android.maps.ItemizedOverlay.onTap(ItemizedOverlay.java:455)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at offscreen.tagger.Main$PoiOverlay.onTap(Main.java:306)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at com.google.android.maps.OverlayBundle.onTap(OverlayBundle.java:83)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at com.google.android.maps.MapView$1.onSingleTapUp(MapView.java:347)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at com.google.android.maps.GestureDetector.onTouchEvent(GestureDetector.java:533)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at com.google.android.maps.MapView.onTouchEvent(MapView.java:647)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at android.view.View.dispatchTouchEvent(View.java:3765)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:905)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:944)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:944)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:944)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:944)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:1701)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1116)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at android.app.Activity.dispatchTouchEvent(Activity.java:2093)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:1685)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at android.view.ViewRoot.handleMessage(ViewRoot.java:1802)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at android.os.Handler.dispatchMessage(Handler.java:99)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at android.os.Looper.loop(Looper.java:144)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at android.app.ActivityThread.main(ActivityThread.java:4937)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at java.lang.reflect.Method.invokeNative(Native Method)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at java.lang.reflect.Method.invoke(Method.java:521)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
02-06 15:37:45.749: ERROR/AndroidRuntime(3215): at dalvik.system.NativeStart.main(Native Method)
You mention earlier “I already did that” and talk about the add method. But the most important two lines that I added to your code were in the *remove* method:
@Override
protected boolean onTap(int index) {
int id=mOverlays.get(index).id;
mOverlays.remove(index);
// Try adding these lines:
setLastFocusedIndex(-1);
populate();
return true;
}
As I mention in the post, any time you structurally modify the internal list you first call setLastFocusedIndex(-1), then call populate.
If those lines exactly as I wrote them are in your code and you’re still getting the problem, then I’m not sure what’s going on.
I added the two lines as you suggested me, but I’m still having the problem. Maybe have I to remove all the overlays and re-adding all of them but not the selected one ??
You sir are in my debt!
Thanks.. I think?
@i82much: HI, very good explanation of the problem. But can you please post the code to synchronize the methods?(Because i have to remove OverlayItems from a Timer thread)
Very good question. I never had to periodically remove the items, and I haven’t investigated the best way to synchronize it. You could probably add synchronized(mOverlays) block around the bodies of the three methods that access/modify the list.
I tried that (“add synchronized(mOverlays) block around […]”) and synchronized my other methods, which modify the mOverlays list. But then i get another exception:
04-08 14:09:58.949: ERROR/AndroidRuntime(4311): FATAL EXCEPTION: main
04-08 14:09:58.949: ERROR/AndroidRuntime(4311): java.util.ConcurrentModificationException
04-08 14:09:58.949: ERROR/AndroidRuntime(4311): at java.util.ArrayList$ArrayListIterator.next(ArrayList.java:573)
04-08 14:09:58.949: ERROR/AndroidRuntime(4311): at com.google.android.maps.OverlayBundle.draw(OverlayBundle.java:44)
04-08 14:09:58.949: ERROR/AndroidRuntime(4311): at com.google.android.maps.MapView.onDraw(MapView.java:494)
04-08 14:09:58.949: ERROR/AndroidRuntime(4311): at android.view.View.draw(View.java:6740)
04-08 14:09:58.949: ERROR/AndroidRuntime(4311): at android.view.ViewGroup.drawChild(ViewGroup.java:1640)
04-08 14:09:58.949: ERROR/AndroidRuntime(4311): at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367)
04-08 14:09:58.949: ERROR/AndroidRuntime(4311): at android.view.View.draw(View.java:6743)
Is that ArrayList being exposed anywhere else?
Thanx man!
You’re welcome
I solved my problem. The solution was not to call the UI thread from another thread (as it mentioned here before i think :-/). Anyway, I post a link to a page, which describe the possible ways to solve the problem: This link provides links to a page where you can find links to other pages (how to use handlers, painless threading etc..
Ssi-bal Thank you. I solved !!! 🙂 씨밝 너 천재 너 짱묵어 !! 감사! 쌩유! 넌 천재!
Thanks man, you save my life. I was almost given up to put a new feature at my software because i can`t solve this bug. Thanks a lot…
You are awesome! These two The ArrayIndexOutOfBoundsException problem was really bugging me.
Thanks!!!
cheers dude, you made my day !
Really helpful, thanks a lot.
Cheers
Oh wow, thanks a lot. Saved me a lot of time !!
OMG, I just spend 4 hours looking for solution!
Thanks a lot!
Thanks a million times for the setLastFocusedIndex(-1) fix! You saved me hours of debugging.
hi thanks a lot for this! unfortunately, i still have this ArryIndexOutOfBound error you described when running my code: http://pastebin.com/9giTshz4
When I add some markers on the map, I can all of them except the one I added at last. The last one added causes this error:
07-31 16:16:34.187: ERROR/AndroidRuntime(1355): Uncaught handler: thread main exiting due to uncaught exception
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): java.lang.IndexOutOfBoundsException: Invalid location 0, size is 0
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at java.util.ArrayList.get(ArrayList.java:353)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at com.google.android.maps.ItemizedOverlay.getItem(ItemizedOverlay.java:419)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at com.google.android.maps.ItemizedOverlay.focus(ItemizedOverlay.java:538)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at com.google.android.maps.ItemizedOverlay.onTap(ItemizedOverlay.java:455)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at com.google.android.maps.OverlayBundle.onTap(OverlayBundle.java:83)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at com.google.android.maps.MapView$1.onSingleTapUp(MapView.java:346)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at android.view.GestureDetector.onTouchEvent(GestureDetector.java:503)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at com.google.android.maps.MapView.onTouchEvent(MapView.java:623)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at android.view.View.dispatchTouchEvent(View.java:3368)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:831)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:863)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:863)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:863)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:1707)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1197)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at android.app.Activity.dispatchTouchEvent(Activity.java:1993)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:1691)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at android.view.ViewRoot.handleMessage(ViewRoot.java:1525)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at android.os.Handler.dispatchMessage(Handler.java:99)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at android.os.Looper.loop(Looper.java:123)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at android.app.ActivityThread.main(ActivityThread.java:3948)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at java.lang.reflect.Method.invokeNative(Native Method)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at java.lang.reflect.Method.invoke(Method.java:521)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:782)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:540)
07-31 16:16:34.208: ERROR/AndroidRuntime(1355): at dalvik.system.NativeStart.main(Native Method)
can you help me? THX!!
I’ve exactly the same problem.
Did you find a solution ?
Yes! This is exactly the problem I am having as well. If someone found a solution to this it would be great!
Hi,
I was having no solution to this issue until I found this valuable post. Thanks a lot. Keep up the good work.
You are a hero!!!
Thanks a lot man !!! I spent many hours with no result, and now I find here a solutions. Thanks again!
Guys synchronized size() methode lock -it if index <-1 unlock it when adding or remiving overlay, it soles the problem
Awsome! Thanks for the arrayindexoutofboundsexception fix!
You are my God!
The Google Maps API on Android is one giant ball of fail.
– Loads of completely unexplained, undocumented generic exceptions which bubble up from the depths of a closed-source code base.
– Completely substandard documentation (when compared to the rest of the Android API).
– No support for terrain, street view, and various other layers.
– No support for the new Fragments API.
I just don’t get why Google allows this to be the case. They can’t expect developers to create innovative apps which help the platform to compete with iOS if basic APIs like this one are so poor.
It’s all the more perplexing when you consider that the Maps ecosystem is owned by Google, It’s not like they have to deal with some third party.
Interesting -thanks for this.
@Tom, the last poster, I’ve been having very similar thoughts to you – why are Google allowing this API to languish in such a sub-standard condition yet providing iOS with a vastly superior maps interface?
I do have a theory – look at the sheer development pace of their own, native Maps application for Android. In recent months, they’ve added loads of new features – restaurant searches, bus times display, etc. So it looks to me like they’re trying to make their Maps app the first ‘go to’ place for anything location-based. By witholding API features from developers, they’re able to get ahead of the competition, so as to speak!
It’s a valuable (financially speaking) thing for them to own – think of the money to be made from sponsored location-based listings, etc. Google wants you to click their Maps app to find local things, not some 3rd party app which waters down their ad deals.
Yes, it works !
Thank you !
Thank You!!!
Thanks! btw is Google maps not open source? I thought all android was…
That is amazing, thanks for the great write up and taking the time to document!!
You’re very welcome. It’s comments like yours that motivate me to keep writing
Thanks so much for this man … you’re awesome!
Thanks man! You’re a lifesaver!
Thanks a lot. This indeed is the top google result for “MapView ArrayIndexOutOfBounds exception” :).
Thanks a lot, I solved my problem 🙂
Thank you very much.
You sir, are a life-saver. Thank you. setLastFocusedIndex(-1) was the missing piece of the puzzle.
Thank you very much.It helped me a lot.Saved my day.cheers 🙂
Thank you!
Dear god thank you for this fix
Thx
you saved my life man!!!! thanks!!!
A million thanks. Made my day 🙂
Thank you, this helped me!