Twitter: raymondcamden


Address: Lafayette, LA, USA

Working with Google Maps in Flex Mobile

05-21-2011 13,841 views Mobile, Flex 12 Comments

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

12 Comments

  • Ben Dalton #
    Commented on 05-21-2011 at 2:50 PM
    Ray,

    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.
  • Commented on 05-21-2011 at 5:08 PM
    Here was my solution: http://chrisgriffith.wordpress.com/2011/01/04/inte...

    In a nutshell, I used an instance of StageWebView and loaded a web version of the Google Map.
  • Commented on 05-23-2011 at 11:18 AM
    @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?
  • Ed #
    Commented on 05-23-2011 at 11:46 AM
    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?)
  • Commented on 05-23-2011 at 11:54 AM
    My spam checker can be a bit over the top sometimes - can you post a goo.gl version?
  • Commented on 05-23-2011 at 5:04 PM
    Holy crap Ben. I just tried the fix, and it worked perfectly. Scary perfectly.
  • Nepol #
    Commented on 10-03-2011 at 2:16 PM
    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!
  • Commented on 10-04-2011 at 8:41 AM
    There is a Download link at the end of the entry.
  • Brian Reed #
    Commented on 11-03-2011 at 9:58 PM
    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.com/web/products/feature...
  • xhe #
    Commented on 11-12-2011 at 9:41 AM
    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.
  • Fher #
    Commented on 12-29-2011 at 10:21 PM
    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????....
  • Commented on 12-29-2011 at 10:46 PM
    ios definitely supports AIR. Not sure what to recommend though. Google is no longer supporting Flex for maps.

Post Reply

Please refrain from posting large blocks of code as a comment. Use Pastebin or Gists instead. Text wrapped in asterisks (*) will be bold and text wrapped in underscores (_) will be italicized.

Leave this field empty