Dynamically Loading Google Maps on the Palm Pre

Tue, 06 Oct 2009 19:51:00 -0700

One problem that I had to overcome while writing the OneBusAway app for the Palm Pre was figuring out how to provide Google Maps integration w/o drastically affecting startup time for the app. Since there is no direct support from the Palm API for Google Maps embedding, you have to load the Javascript API directly from Google. This of course involves a potentially lengthy download especially if you have a spotty connection. One way to solve this is to load the Google Maps API only when you actually use it in your app (in this case I only load the API when the user actually goes to a maps view of the nearby bus stops).

Luckily Google provides an easy way to load their APIs dynamically the only trick is to make sure that the DOM is available before calling any of them. On the Pre this means that you must wait until the Setup method on one of your assistants before loading the APIs.

Sample code and more info after the break.

I first created a helper class to manage loading the API’s and provide me with an easy way to ensure that the API only loads once.

function MapsHelper() {
    this.initialized = false;

}
MapsHelper.prototype.isLoaded = function()
{
    return this.initialized;
}
MapsHelper.prototype.mapsLoaded = function(id)
{
    try
    {
        Mojo.Log.error("Initializing Maps");

        // Load any map objects such as Icons that you will reuse later

        Mojo.Log.error("Maps Initialized");

        this.initialized = true;
        if(this.loadCallback != undefined)
        {
            this.loadCallback();
        }


    } catch(error) {
        Mojo.Log.error("Error Initializing Maps: " + error);
    }
};

MapsHelper.prototype.createMap = function(id)
{
    var map = new GMap2($(id));
    return map;
};


MapsHelper.prototype.loadedCallback = function(callback)
{
    this.loadCallback = callback;
}

var Maps = new MapsHelper();

function loadMaps() {

    Mojo.Log.error("Initializing Google Maps");
    google.load("maps", "2", {"callback" : Maps.mapsLoaded.bind(Maps)});
}

function initLoader() {

    Mojo.Log.error("Initializing Google Loader");
    // Code from Google Sample
    var script = document.createElement("script");
    script.src = "http://www.google.com/jsapi?key=YOUR_API_KEY&callback=loadMaps";
    script.type = "text/javascript";
    document.getElementsByTagName("head")[0].appendChild(script);
}

Here I have one global function “initLoader” that kicks off the api loading. In addition there is a single global instance of MapsHelper called Maps. This provides a way to set a callback for when loading is completed and a function to determine if the maps api has already been loaded before.

Now in the assistant that needs to use the maps object I have the following code to handle when the user taps the Map View Command:

if (this.map === undefined)
{
    // Kick off google maps initialization
    if(!Maps.isLoaded())
    {
        this.spinnerModel.spinning = true;
        this.controller.modelChanged(this.spinnerModel);

        this.controller.get("statusinfo").update("Loading Google Maps...");

        Maps.loadedCallback(this.initMap.bind(this))
        initLoader();
    }
    else
    {
        this.initMap();
    }
}

This code first checks to see if the Maps API has been loaded and if not it sets the callback to my ‘initMap’ function on the assistant otherwise I just call initMap. You will also notice that I have a spinner that I activate to show when the maps api is loading.

Finally in initMap I actually create the map object and place my markers and am ready to go. In all future calls to this page we can bypass loading the api and the map will show up right away.

this.map = Maps.createMap('map_canvas');

// Setup event handlers etc.

this.spinnerModel.spinning = false;
this.controller.modelChanged(this.spinnerModel);

this.controller.get("statusinfo").update("");

Mojo.Log.error("Map Created");

// Create markers and setup any other data