Shit. Did you just click on an article titled “How to pass complex data to custom InfoWindows on a GoogleMap”? I’m impressed by your thirst for knowledge.
An InfoWindow is the thing that pops up when you tap a marker on a GoogleMap object on Android. The InfoWindow contains (unsurprisingly) info, which is usually relevant to the location the marker is placed at. Here is how a default InfoWindow looks (screenshot taken from my app, Looxie)
See that “Level 4 user: 2059 points collected” thing? That’s how the default InfoWindow looks.
Sometimes, that won’t work. For example, you may want to place an image on the InfoWindow. Or perhaps set a custom typeface on it so that it’s consistent with the look of the rest of your app. For example, this is what I wanted the InfoWindow to look like in my latest experimental app
See that sweet Bariol font at work? And the cute “Play” button? That required the use of a custom InfoWindow. Custom InfoWindows are not really the point of this article (but rather the info that’s passed to the InfoWindow is) but here’s are some steps, for the uninitiated
<fragment android:id=”@+id/map”android:name=”com.google.android.gms.maps.SupportMapFragment” android:layout_width=”match_parent”android:layout_height=”match_parent” />
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() .findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
If your setup went OK, you should now see a Google Map in the fragment.
To start placing and manipulating markers on the Google Map, it’s always a good idea to have the Activity that’s hosting the map implement the OnMapReadyCallback interface. This will allow you (force you, actually) to implement a callback method called onMapReady() in your Activity, which will execute as soon as the map is loaded and ready.
public void onMapReady(GoogleMap map) {
}
This method passes a GoogleMap instance to you, which you can then use to perform various operations on the map.
You can now place a marker on the map (in onMapReady()) by doing the following
Marker marker = map.addMarker(new MarkerOptions().position(latLng) .icon(BitmapDescriptorFactory.fromResource(iconResource)) .title(title).snippet(snippet));
The bolded variables are, respectively,
This will place a Marker with a default InfoWindow on the map. However, as everybody knows
Default InfoWindows are ugly. Default InfoWindows are bo-ring. When default InfoWindows show up at InfoWindow School they get bullied by cooler, more customized InfoWindows. Do you want your InfoWindows to get bullied, you savage?
That’s a straight quote from the “What to expect when you are placing Markers on a Google Map” book, so shut up and follow my lead.
To create your own customized InfoWindow, you create your own class that implements GoogleMap.InfoWindowAdapter and then override two methods: getInfoContents() and getInfoWindow()
The difference between these two methods is that getInfoContents() provides a default “bubble”, shadow and caret that points towards the marker (see my first screenshot above) while getInfoWindow() provides nothing, like that stingy uncle of yours at a family vacation in 1998, when you were at the pool and wanted a freaking ice cream but he was too cheap to get you one.
[deep breaths]
When you override getInfoWindow() you provide the entire layout, background and all by inflating an XML layout. Here is what an implementation of InfoWindowAdapter looks like
public class InfoWindowCustom implements GoogleMap.InfoWindowAdapter {
Context context;
LayoutInflater inflater;
public InfoWindowCustom(Context context) {
this.context = context;
}
@Override
public View **getInfoContents(Marker marker)** {
return null;
}
@Override
public View **getInfoWindow(Marker marker)** {
inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// R.layout.echo_info_window is a layout in my// res/layout folder. You can provide your own
View v = inflater.inflate(_R.layout.echo\_info\_window_, null);
TextView title = (TextView) v.findViewById(R.id.info\_window\_title);
TextView subtitle = (TextView) v.findViewById(R.id.info_window_subtitle);
title.setText(marker.getTitle());
subtitle.setText(marker.getSnippet());
return v;
}
}
Meanwhile, back in the magical land of onMapReady() in the Activity where the map is shown, you have to set the InfoWindowAdapter implementation on the map, like this
map.setInfoWindowAdapter(new InfoWindowCustom(this));
From now on, whenever a marker is tapped, all the marker info will be beautifully displayed, framed by your delightful custom InfoWindow.
BUT WAIT! That’s not what I promised! What I promised was the ability to pass complex data to the InfoWindow adapter, which sort of seems difficult right now, since the marker can only carry two pieces of data: a title and a snippet.
When I first came upon this problem, my solution was to concatenate everything I wanted to display into the snippet, with line breaks (“\n”) to seperate different lines that would represent different data.
That meant, however, that I would have to mess around with Spannables and String lengths if I wanted to style the various pieces of data differently. Have you ever worked with Spannables on concatenated String data? I have and I’m pretty sure that anal violation by Satan is a more exciting prospect.
Nope. I needed something simpler. And then suddenly my eyes lit up and I felt like freakin’ Werner Einsten when he discovered Marxism.
Why couldn’t I simply pass a JSON representation of an object to the snippet and then retrieve that snippet in the InfoWindowAdapter and turn it back into an object?
Turns out I could. And I did.
Let’s say you have this class called MarkerInfo
public class MarkerInfo {
private String title;
private String subtitle;
private String soundUrl;
private int numberOfPlays;
public MarkerInfo() { }
// getters and setters omitted because I'm not writing this in// an IDE. But assume that they are there
}
A super-easy way to turn an object of this class into a JSON representation and back into an object is to use a library like Gson.
After importing it into your project, you can just turn a MarkerInfo object into a String (JSON) representation simply by doing this
MarkerInfo markerInfo = new MarkerInfo();markerInfo.setTitle("Shit went down");markerInfo.setSubtitle("In Hell's Kitchen");markerInfo.setSoundUrl("https://www.luke.com/matthew/jessica/dumbass.mp3");markerInfo.setNumberOfPlays(66);
Gson gson = new Gson();String markerInfoString = gson.toJson(markerInfo);
So when you’re setting the snippet property on your Marker, you pass the resulting String to it and then in your custom InfoWindowAdapter class, you retrieve the String representation of the object and turn it back into an object.
Gson gson = new Gson();
MarkerInfo aMarkerInfo = gson.fromJson(marker.getSnippet(), MarkerInfo.class);
Now, you can access any object field individually and set it on your views as you see fit.
Cool, huh? Of course, after some research on the Internet using the correct keywords, I found out that other people had used this trick prior to my “discovery” but fuck those guys because they are not me.