How to pass complex data to custom InfoWindows on a GoogleMap

Worst. Blog. Post. Title. Ever.

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

First, add the Google Play Services and Google Maps SDK by following this tutorial. You’ll also have to get an API key.

After you’ve done that, you’ll have to add a <fragment> in your layout with the attributes you want it to have and add the bolded line to it

<fragment android:id=”@+id/map”

android:name=”com.google.android.gms.maps.SupportMapFragment”

android:layout_width=”match_parent”

android:layout_height=”match_parent” />

This will allow you to cast the <fragment> to a SupportMapFragment in your Java code, like this

SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() .findFragmentById(R.id.map);

Finally, show the map in the fragment by calling getMapAsync() on the fragment and passing in the current Activity context

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,

a LatLng object that contains your latitude and longitude

an icon resource, eg. R.drawable.my_icon

a String variable that contains the marker title

another String variable that holds extra info about the marker, called a snippet. Remember this one, it’s important.

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.

A Promise Fulfilled on Russian Hill

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 (“

”) 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.