Twitter: raymondcamden


Address: Lafayette, LA, USA

Working with Google Maps in Flex Mobile

05-21-2011 13,896 views Mobile, Flex

So - I wasn't sure if I should post this blog entry. Why? What I'm describing is a bit of a failure. But the more I thought about it the more I figured that the experience I went through could be useful for others. Plus - there's always the chance that I made a simple mistake and someone will point it out. I then get to pretend that it was a test and quickly fix my code! That being said - what I'm about to describe should not be something you should try yourself. At least not in the same way I did. Ok - so enough preamble!

I decided to add one more simple feature to the INeedIt mobile application: driving directions. This is a topic I've covered before with JavaScript and jQuery Mobile, so I was curious as to how it would work in the Flex world. I knew that Google had an API for Flash and I'd heard good things about it. I figured I'd replace the static map image I had now while also adding in the driving directions.

I spent maybe 15 minutes looking over the docs. For the most part, it's incredibly easy. You download a SWC and add it to your project. (Total time - 60 seconds.)

You then drop a map tag into your Flex code. (Total time - 60 seconds.)

view plain print about
1<maps:Map xmlns:maps="com.google.maps.*" id="map" mapevent_mapready="onMapReady(event)" width="100%" height="100%" key="ABQIAAAANa2eDzLCrHnNGWxQ6nsUqBT0kcRajLdLCyGsAW6MRHnr7QX6dBTzuJkY4CKmc-3TB-8A2-9DEW3IXQ" sensor="false" url="http://coldfusionjedi.com" />

You then write an event handler to listen for the map ready event. Now - for me - this was the most tricky part. I use the map on a view that fires off a call to get detailed information about a Google Place data item. I basically have 2 network calls then. One for my Place data and one for the map to prepare itself. This is probably not the best way to do it - but I used variable flags to handle checking to see if the "other guy" was done. Here are the two functions:

view plain print about
1private function placeResult(event:ResultEvent):void {
2    var resultData:Object = JSON.decode(event.result.toString());
3    addressLabel.text = resultData.result.formatted_address;
4    phoneLabel.text = resultData.result.formatted_phone_number;
5    
6    //Store long/lat for use for driving directions
7    long = resultData.result.geometry.location.lng;
8    lat = resultData.result.geometry.location.lat;
9    //technically this could finish before mapReady - need to think
10    if(mapReady) drawMap();
11    trace("done ");    
12}
13                    
14protected function onMapReady(event:MapEvent):void {
15    if(lat) drawMap();
16    mapReady = true;
17}

Basically, when placeResult is done it checks to see if the map is done. If so, it calls drapMap. onMapReady checks for the existence of lat, the latitude of the place result. If that exists, then it runs drawMap. This was the only real complex part to my code. (Total time: 5 minutes.)

Actually drawing the map then was pretty trivial. I used one simple function to center it on my Place result and added a marker as well. (Total time: 5 minutes.)

view plain print about
1protected function drawMap():void {
2    map.setCenter(new LatLng(lat,long), 16, MapType.NORMAL_MAP_TYPE);
3    var markerA:Marker = new Marker(new LatLng(lat, long),
4        new MarkerOptions({
5            strokeStyle: new StrokeStyle({color: 0x987654}),
6            fillStyle: new FillStyle({color: 0x223344, alpha: 0.8}),
7            radius: 12,
8            hasShadow: true
9        }));
10    map.addOverlay(markerA);
11}

Woot! Pretty simple, right? Check out the result:

Unfortunately, this is where we hit a brick wall. On the simulator I noticed an odd pause when the place view was loaded. I figured it was a gremlin and didn't pay attention. When I tried it on my mobile device however - whoa buddy. Hitting the link to load a Place result now took about 15-30 seconds to load! Turns out Google Maps delays the loading of it's libraries. No wonder. If it takes that long to load I can see why. Unfortunately this pretty much kills the usability of the application. It seems like maybe there should be a way for everything else to happen except for the map loading until the view is there, but even if I didn't run drawMap, the mere existence of the Map tag caused the incredible slow down.

Failure.

That sucks... but I decided to move on. Mainly because I wanted to see the driving directions anyway. Much like my jQuery Mobile example, I went for a text page approach. But unlike my old example that showed one step at a time, I figured I'd simply list out the results. I made some modifications to my code so that I could pass to my new view both the user's location and the place result location. Here's what I came up with:

view plain print about
1<?xml version="1.0" encoding="utf-8"?>
2<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
3        xmlns:s="library://ns.adobe.com/flex/spark" title="Driving Directions" viewActivate="init(event)">

4    <fx:Script>
5        <![CDATA[
6            import com.google.maps.LatLng;
7            import com.google.maps.services.Directions;
8            import com.google.maps.services.DirectionsEvent;
9            import com.google.maps.services.Route;
10            import com.google.maps.services.Step;
11            
12            import spark.events.ViewNavigatorEvent;
13
14            private function goBack(event:MouseEvent):void {
15                navigator.popView();
16                //hollyschinsky@gmail.com
17            }
18
19            protected function init(event:ViewNavigatorEvent):void {
20
21                trace(data.long+","+data.lat);
22                var dir:Directions = new Directions();
23                dir.addEventListener(DirectionsEvent.DIRECTIONS_SUCCESS, onDirLoad);
24                dir.addEventListener(DirectionsEvent.DIRECTIONS_FAILURE, onDirFail);
25                
26                var waypoints:Array = [new LatLng(data.mylat,data.mylong),
27                                             new LatLng(data.lat,data.long)
28                                                ];
29                dir.loadFromWaypoints(waypoints);
30                statusLabel.text = "Loading directions...";
31            }
32            
33            protected function onDirLoad(event:DirectionsEvent):void
34            {
35                currentState="ready";
36                trace('got stuff back');
37                var directions:String = "";
38                var directionData:Directions = Directions(event.directions);
39            
40                //As before, use route 1
41                if(directionData.numRoutes >
= 1) {
42                    var route:Route = directionData.getRoute(0);
43                    for(var i:int=0; i<route.numSteps; i++) {
44                        var step:Step = route.getStep(i);
45                        var html:String = step.descriptionHtml;
46                        html = html.replace(/<div class="google_note">/g,"\n\n");
47                        directions += html.replace(/<.*?>/g,"") + "\n\n";
48                    }
49                    trace(directions);
50                    dirBox.text = directions;
51                    
52                } else {
53                    //A lie, but close enough
54                    currentState="error";
55                    statusLabel.text = "Sorry, unable to get directions.";
56                    
57                }
58                
59            }
60            
61            protected function onDirFail(event:Event):void
62            {
63                currentState="error";
64                statusLabel.text = "Sorry, unable to get directions.";
65                trace('dir error');
66                
67            }
68            
69        ]]>

70    </fx:Script>
71    <fx:Declarations>
72        <!-- Place non-visual elements (e.g., services, value objects) here -->
73    </fx:Declarations>
74
75    <s:actionContent>
76        <s:Button label="Back" click="goBack(event)" />        
77    </s:actionContent>
78
79    <s:layout>
80        <s:VerticalLayout gap="5" paddingTop="5" paddingLeft="5" paddingRight="5"/>
81    </s:layout>
82
83    <s:states>
84        <s:State name="loading" />
85        <s:State name="error" />
86        <s:State name="ready" />
87    </s:states>
88
89    <s:Label id="statusLabel" includeIn="error,loading" />
90    <s:Label id="dirBox" width="100%" height="100%" includeIn="ready" />
91</s:View>

I love how easy Google's directions API are. In case it isn't obvious in the code above, this is all you really need:

view plain print about
1var dir:Directions = new Directions();
2dir.addEventListener(DirectionsEvent.DIRECTIONS_SUCCESS, onDirLoad);
3dir.addEventListener(DirectionsEvent.DIRECTIONS_FAILURE, onDirFail);
4
5var waypoints:Array = [new LatLng(data.mylat,data.mylong),
6                 new LatLng(data.lat,data.long)
7                ];
8dir.loadFromWaypoints(waypoints);

That's pretty darn simple, right? Once the directions are returned, I can simply loop over them and make a big ole string I then dump out to screen. Like so:

So... what to do? There are other mapping packages out there. I could switch. But I don't know if any of them provide directions support. I think I may switch back to the Google Static Map API and simply get rid of the driving directions. Kinda sucks, but, it's still a cool application. Any opinions out there? Remember that you can get all of the code via the Github repo. If you want to try the APK yourself, I've attached it to this blog post.

Download attached file

Related Blog Entries