Android-maps-utils: How to get a marker knowing its ClusterItem and vice versa?

Created on 30 Jan 2014  路  10Comments  路  Source: googlemaps/android-maps-utils

First at all, congratulations!!! Great job (we users ask for something but not thanks given). I started to use this library for few days ago (clustering) and I need to show markers in map, and when user click on them, it must to show the info window showing some information about the marker. Inside this information there is a photo profile from url (http...). As you know, the photo has to be taken in a background task, so when onClusterItemClick listener go out, it downloads the photo, and when it ends, callback has to show info window, so in onClusterItemClick listener interface I return true ( I have managed the event and this way info window is not showing) but when callback from image download has to show info window but it can not call showInfoWindow method from a marker object because I have no reference to it. Also, I need to consult some information from cluster in InfoWindowAdapter, and in this case, I know the marker but not the clusterItem information.

As solution, I have tried to give two method to DefaultClusterRenderer class:

    protected Marker getMarker(T item) {
    return mMarkerCache.get(item);
}

protected T getCluster(Marker marker) {
    return mMarkerCache.get(marker);
}

This way I resolve my problem and every was fine until I recived this crash report:

01-30 17:42:50.685: E/ACRA(15290): ACRA caught a ConcurrentModificationException exception for info.si2.android.unidogs.app. Building report.
01-30 17:42:53.513: E/AndroidRuntime(15290): FATAL EXCEPTION: Thread-8895
01-30 17:42:53.513: E/AndroidRuntime(15290): java.util.ConcurrentModificationException
01-30 17:42:53.513: E/AndroidRuntime(15290): at java.util.HashMap$HashIterator.nextEntry(HashMap.java:792)
01-30 17:42:53.513: E/AndroidRuntime(15290): at java.util.HashMap$KeyIterator.next(HashMap.java:819)
01-30 17:42:53.513: E/AndroidRuntime(15290): at java.util.AbstractSet.removeAll(AbstractSet.java:110)
01-30 17:42:53.513: E/AndroidRuntime(15290): at com.google.maps.android.clustering.view.DefaultClusterRenderer$RenderTask.run(DefaultClusterRenderer.java:383)
01-30 17:42:53.513: E/AndroidRuntime(15290): at java.lang.Thread.run(Thread.java:856)

Are this and my methods relationated or is this a independient issue?
Can you confirm me this methods are a good idea in the library?

Thank you, I hope to hear soon about you.

Most helpful comment

To track any data associated with the marker, it is best to create a hashmap of marker ID vs. you data model, the best place to implement this is also in the renderer class by implementing the method:

@Override
protected void onClusterItemRendered(Person person, Marker marker) {
    super.onClusterItemRendered(person, marker);
    markerPersonMap.put(marker.getId(), person);
}

When implementing the method, onBeforeClusterItemRendered, you have MarkerOptions object passed in as the second parameter. You can then modify it from there.

markerOptions.title("Blah")
                      .snippet("some property from the first parameter object...")
                     .flat(true);

and finally, on your onInfoWindowClick method, you can access your object (ClusterItem) by doing

    @Override
    public void onInfoWindowClick(Marker marker) {
         final Person thePersonOnThisMarker = markerPersonMap.get(marker.getId());
         // do something with this person/ClusterItem associated with this marker / markerInfoWindow
    }

All 10 comments

To track any data associated with the marker, it is best to create a hashmap of marker ID vs. you data model, the best place to implement this is also in the renderer class by implementing the method:

@Override
protected void onClusterItemRendered(Person person, Marker marker) {
    super.onClusterItemRendered(person, marker);
    markerPersonMap.put(marker.getId(), person);
}

When implementing the method, onBeforeClusterItemRendered, you have MarkerOptions object passed in as the second parameter. You can then modify it from there.

markerOptions.title("Blah")
                      .snippet("some property from the first parameter object...")
                     .flat(true);

and finally, on your onInfoWindowClick method, you can access your object (ClusterItem) by doing

    @Override
    public void onInfoWindowClick(Marker marker) {
         final Person thePersonOnThisMarker = markerPersonMap.get(marker.getId());
         // do something with this person/ClusterItem associated with this marker / markerInfoWindow
    }

First thanks, but I need to resolve some questions:

1.- Why is better to create a hashmap if inside library there are two structures which have those data, why don't reuse them to get info? The way you commented resolves just one question: given a marker -> get their data, but what will happen when you need a marker from data? I know I can create another hashmap inverse, but I don't know if it is a good idea repeat the same structure than library has.

2.- Yes, I can manage some information about marker in onBeforeClusterItemRendered like snippet, title, etc but I need to call showInfoWindow method from a marker in a determinate time, so I need to get a marker from cluster data.

By the way, crash report was because of sometimes I called showInfoWindow from marker when zoom could had changed and marker didn't exist. I resolved that and now all is working but I would like to know why is bad idea (may be not) giving two method to DefaultClusterRenderer class:

protected Marker getMarker(T item) {
return mMarkerCache.get(item);
}

protected T getCluster(Marker marker) {
return mMarkerCache.get(marker);
}

I agree with you. Inside the static class MarkerCache, there is already a mCacheReverse that stores this 'map'. I'm assuming this is what you're trying to expose with your methods above. I am not yet clear on the entire architecture of this library though, so I cannot comment whether this is OK or not. It seems like they used the variable mMarkerCache to manage markers that are added/removed when needed (i.e. zoom, after animation, etc...) . The removeMarker method, is even marked private .... So maybe as long as you check if the marker is still there or not ....

This is a good point - I think initial design concerns were misguided - the clusterer has no concept of markers intentionally. I can see that has hampered usability.

The idea to add those protected methods sounds good to me.

Would you like to send a pull request to expose those?

Pull request sent

onClusterItemRendered method gets called only when we zoom map .

Thanks for the pull requests, all. This is in 0.3.2.

Oh yes, making these maps publicly accessible to get ClusterItem for Marker is a must! This helped me find that mapping in the DefaultClusterRenderer though.

How else would you be able to update a ClusterItem's position when dragged for example? You only have the Marker in the OnMarkerDragListener. The problem with rolling your own map, besides duplicating an already present and managed cache, is that Markers are never removed from your map when they're removed from the cluster renderer! So this would surely leak memory.

libary ClusterItem where ?
http://www.chuanroi.net/

@ericpanorel Thank you so much!!!!!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bedzinsa picture bedzinsa  路  7Comments

kujaomega picture kujaomega  路  4Comments

dadino picture dadino  路  7Comments

mengoni picture mengoni  路  4Comments

syedalibumpin picture syedalibumpin  路  5Comments