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.)
<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:
//Store long/lat for use for driving directions
long = resultData.result.geometry.location.lng;
lat = resultData.result.geometry.location.lat;
//technically this could finish before mapReady - need to think
if(mapReady) drawMap();
trace("done ");
} protected function onMapReady(event:MapEvent):void {
if(lat) drawMap();
mapReady = true;
}
private function placeResult(event:ResultEvent):void {
var resultData:Object = JSON.decode(event.result.toString());
addressLabel.text = resultData.result.formatted_address;
phoneLabel.text = resultData.result.formatted_phone_number;
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.)
protected function drawMap():void {
map.setCenter(new LatLng(lat,long), 16, MapType.NORMAL_MAP_TYPE);
var markerA:Marker = new Marker(new LatLng(lat, long),
new MarkerOptions({
strokeStyle: new StrokeStyle({color: 0x987654}),
fillStyle: new FillStyle({color: 0x223344, alpha: 0.8}),
radius: 12,
hasShadow: true
}));
map.addOverlay(markerA);
}
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:
<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark" title="Driving Directions" viewActivate="init(event)">
<fx:Script>
<![CDATA[
import com.google.maps.LatLng;
import com.google.maps.services.Directions;
import com.google.maps.services.DirectionsEvent;
import com.google.maps.services.Route;
import com.google.maps.services.Step; import spark.events.ViewNavigatorEvent; private function goBack(event:MouseEvent):void {
navigator.popView();
//hollyschinsky@gmail.com
} protected function init(event:ViewNavigatorEvent):void { trace(data.long+","+data.lat);
var dir:Directions = new Directions();
dir.addEventListener(DirectionsEvent.DIRECTIONS_SUCCESS, onDirLoad);
dir.addEventListener(DirectionsEvent.DIRECTIONS_FAILURE, onDirFail); var waypoints:Array = [new LatLng(data.mylat,data.mylong),
new LatLng(data.lat,data.long)
];
dir.loadFromWaypoints(waypoints);
statusLabel.text = "Loading directions...";
} protected function onDirLoad(event:DirectionsEvent):void
{
currentState="ready";
trace('got stuff back');
var directions:String = "";
var directionData:Directions = Directions(event.directions); //As before, use route 1
if(directionData.numRoutes >= 1) {
var route:Route = directionData.getRoute(0);
for(var i:int=0; i<route.numSteps; i++) {
var step:Step = route.getStep(i);
var html:String = step.descriptionHtml;
html = html.replace(/<div class="google_note">/g,"\n\n");
directions += html.replace(/<.*?>/g,"") + "\n\n";
}
trace(directions);
dirBox.text = directions; } else {
//A lie, but close enough
currentState="error";
statusLabel.text = "Sorry, unable to get directions."; } } protected function onDirFail(event:Event):void
{
currentState="error";
statusLabel.text = "Sorry, unable to get directions.";
trace('dir error'); } ]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations> <s:actionContent>
<s:Button label="Back" click="goBack(event)" />
</s:actionContent> <s:layout>
<s:VerticalLayout gap="5" paddingTop="5" paddingLeft="5" paddingRight="5"/>
</s:layout> <s:states>
<s:State name="loading" />
<s:State name="error" />
<s:State name="ready" />
</s:states> <s:Label id="statusLabel" includeIn="error,loading" />
<s:Label id="dirBox" width="100%" height="100%" includeIn="ready" />
</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:
var waypoints:Array = [new LatLng(data.mylat,data.mylong),
new LatLng(data.lat,data.long)
];
dir.loadFromWaypoints(waypoints);
var dir:Directions = new Directions();
dir.addEventListener(DirectionsEvent.DIRECTIONS_SUCCESS, onDirLoad);
dir.addEventListener(DirectionsEvent.DIRECTIONS_FAILURE, onDirFail);
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.
Archived Comments
Ray,
Other people have been having similar problems. It looks as though there's a workaround: http://code.google.com/p/gm...
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.
Here was my solution: http://chrisgriffith.wordpr...
In a nutshell, I used an instance of StageWebView and loaded a web version of the Google Map.
@Ben: I'll take a look at your link a bit later.
@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?
Have a look at Igor Costa's blog - he mentions a similar issue and a workaround (it's linked from Fullasagoog: my message is being 'flagged as spam' if I include the link?)
My spam checker can be a bit over the top sometimes - can you post a goo.gl version?
Holy crap Ben. I just tried the fix, and it worked perfectly. Scary perfectly.
Ray,
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!
There is a Download link at the end of the entry.
MapQuest has an API for Flex that is optimized for mobile. I've used it before and it works really well, if you can get past the part that it's MapQuest. http://developer.mapquest.c...
For mapQuest, I found it is slow and response is also slow. I used ios to test, the want the marker to be clickable. In my ipod touch, it is so difficult to click the marker. so I decided to remove it from mobile app.
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.
I have the same problem of Xhe. I cant run the google map app in my ipod i have everyting. But on my Sony tabblet the same code works perfect. Someone have any idea????....
ios definitely supports AIR. Not sure what to recommend though. Google is no longer supporting Flex for maps.