Extending Cordova Google Maps Plugin
While you could use Google Maps JavaScript API in Cordova hybrid mobile applications, native Google Maps SDKs for Android and iOS offer much better user experience. There is a plugin available, to use them from Cordova. If you're using Ionic, there's another wrapper for the plugin to make it even easier to use.
However, using the plugin, you're limited to the functionalities it provides and the way it wraps them for use from JavaScript. Although there's a handy Google Maps utility library available for both Android and iOS with additional features such as marker clustering, you cannot use it from the Cordova plugin.
Of course, you could fork the plugin and extend it, but you don't need to. You can take advantage of the plugin's extensibility model instead. Unfortunately, I couldn't find any documentation for it. I had to learn everything by analyzing the source code of the plugin itself and an unfinished prototype extension I stumbled upon.
This post is a short step-by-step guide to getting your own plugin for the Cordova Google Maps plugin up and running.
Java Code
There are a couple of conventions to follow in Java code:
- The entry class must be placed in the
plugin.google.maps
package, which would typically be theandroid/plugin/google/maps
folder in the plugin folder structure. - The class name must be prefixed with
Plugin
, i.e.PluginClustering
. - The class must extend the
MyPlugin
base class. - The entry methods accept 2 arguments: a
JSONArray
and aCallbackContext
. They don't need to be public at all. It will work even with private methods.
Here's a very simple wrapper for the marker clustering functionality:
package plugin.google.maps;
import com.google.maps.android.clustering.ClusterManager;
import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
import org.json.JSONException;
import plugin.google.maps.clustering.MapClusterItem;
public class PluginClustering extends MyPlugin {
private ClusterManager<MapClusterItem> clusterManager;
private void createCluster(final JSONArray args,
final CallbackContext callbackContext)
throws JSONException {
clusterManager = new ClusterManager<MapClusterItem>(cordova.getActivity(), map);
map.setOnCameraIdleListener(clusterManager);
map.setOnMarkerClickListener(clusterManager);
JSONArray markers = args.getJSONArray(1);
for (int i = 0; i < markers.length(); i++) {
MapClusterItem item = new MapClusterItem(markers.getJSONObject(i));
clusterManager.addItem(item);
}
clusterManager.cluster();
callbackContext.success();
}
}
Except for the JSON parsing, the code is mostly based on the marker clustering documentation. You can also notice my use of cordova.getActivity()
function and map
field provided by the base class to get the reference to activity and map objects.
The library requires you to implement your own ClusterItem
which I've placed in the plugin.google.maps.clustering
package. It parses the standard Google Maps plugin marker options:
package plugin.google.maps.clustering;
import com.google.android.gms.maps.model.LatLng;
import com.google.maps.android.clustering.ClusterItem;
import org.json.JSONException;
import org.json.JSONObject;
public class MapClusterItem implements ClusterItem {
private LatLng position;
private String title;
private String snippet;
public MapClusterItem(JSONObject markerOptions) throws JSONException {
if (markerOptions.has("title")) {
title = markerOptions.getString("title");
}
if (markerOptions.has("snippet")) {
snippet = markerOptions.getString("snippet");
}
JSONObject positionJson = markerOptions.getJSONObject("position");
position = new LatLng(positionJson.getDouble("lat"),
positionJson.getDouble("lng"));
}
@Override
public LatLng getPosition() {
return position;
}
@Override
public String getTitle() {
return title;
}
@Override
public String getSnippet() {
return snippet;
}
}
JavaScript Code
From JavaScript, you won't invoke your method directly. Instead, you'll use the GoogleMaps
plugin exec
method which will pass on the call to your class:
function createCluster(markers, success, error) {
cordova.exec(success, error, 'GoogleMaps', 'exec',
['Clustering.createCluster', markers]);
}
The first argument in the call identifies your class (without the Plugin
prefix) and method.
The rest of the JavaScript code makes the method available to the Cordova application. I based it on the GoogleMaps plugin JavaScript code:
module.exports = {
createCluster: createCluster
};
cordova.addConstructor(function() {
if (!window.Cordova) {
window.Cordova = cordova;
};
window.plugin = window.plugin || {};
window.plugin.clustering = window.plugin.clustering || module.exports;
});
Manifest File
The manifest file is pretty standard:
- The
js-module
element maps the JavaScript file toplugin.clustering
. - The
source-file
elements are required for each source file. They specify where they must be copied to in the final Android project. - The
framework
element adds a reference to the Google Maps utility library.
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
id="plugin.google.maps.clustering" version="0.1">
<name>plugin-google-maps-clustering</name>
<license>Apache 2.0</license>
<js-module src="www/clustering.js" name="clustering">
<clobbers target="plugin.clustering" />
</js-module>
<platform name="android">
<framework src="com.google.maps.android:android-maps-utils:0.5" />
<config-file target="res/xml/config.xml" parent="/*">
<feature name="GoogleMapsClustering">
<param name="android-package"
value="plugin.google.maps.Clustering" />
</feature>
</config-file>
<source-file src="android/plugin/google/maps/PluginClustering.java"
target-dir="src/plugin/google/maps" />
<source-file src="android/plugin/google/maps/clustering/MapClusterItem.java"
target-dir="src/plugin/google/maps/clustering" />
</platform>
</plugin>
The only part I'm not really sure about is the feature
element. The feature name is usually used in the cordova.exec
JavaScript function, but seems unnecessary here since we're invoking the methods through the GoogleMaps plugin. I still left the declaration in the file.
Usage
The plugin first needs to be installed. You don't need to publish the plugin to NPM, you can also pass in a Git URL or a local project subfolder:
cordova platform add ./local-plugins/cordova-plugin-googlemaps-clustering
This will result in the following line being added to Cordova's config.xml
file:
<plugin name="plugin.google.maps.clustering"
spec="./local-plugins/cordova-plugin-googlemaps-clustering" />
In the Cordova application the plugin method will now be available as plugin.clustering.createCluster()
:
var markers = [
{
position: {lat: 46.436705, lng: 14.052606},
title: 'Jesenice'
}
// other markers to be clustered
];
plugin.clustering.createCluster(markers);
Of course, this sample is far from production ready, but it should be enough to base your own Cordova GoogleMaps plugin extension on it.