Working with Google Maps in Flex Mobile
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.)
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:
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.)
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:
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:
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.

Other people have been having similar problems. It looks as though there's a workaround: http://code.google.com/p/gmaps-api-issues/issues/d...
It stems from google maps calculating a SHA1 on some large bytearray. This is an intensive/blocking operation. The workaround simply prevents the calculation from being performed.
If there weren't enough reasons already, this is another example of how Flash could benefit from some form of multithreading.
In a nutshell, I used an instance of StageWebView and loaded a web version of the Google Map.
@Chris: The issue I have with StageWebView is that I don't know how to put it in a view and have it be part of a page. What I mean is - it's simple enough to just put it at 0,0. But then that covers the view's header. So I can probably guess at the right Y value, but then what if I want some label above it? Can you put a SWV in a page so that it lays out with the other items?
Is there a link where I can download the .fxp project with this Get Directions update?
Thanks a lot for all your post I've learned a lot!
For the google map api heck, also it does not work in ios. Works in desktop simulator. Maybe IOs is not providing AIR environment? Someone said android is ok, I will test it later.