Thursday, November 4, 2010

How to use Esri Flex API on Android and iPhone

Update (Jun 2011) : For more updated info, check out my ADC article here.
At MAX2010 (Awesome BTW) Adobe introduced Flex SDK Hero and Flash Builder Burrito which enables you to build and deploy Flex mobile applications on Android by default. In addition, using the revitalized Packager for iPhone, you can postprocess the swf into an ipa that you can deploy on an iPhone using iTunes.

In this post, I will share with you the steps that I took to use the Flex API for ArcGIS to build a mobile mapping application.

First and foremost - RTFM - here are some links that u _need_ to read specially for iPhone provisioning:

http://opensource.adobe.com/wiki/display/flexsdk/Hero
http://labs.adobe.com/technologies/flashbuilder_burrito/
http://labs.adobe.com/technologies/packagerforiphone/

The cool part of this whole process is now you have one code base to build upon and deploy on Android and iPhone and in the future whatever mobile platform Adobe decides to support.
You can download the source code of the sample application from here.
This simple application queries the states demographic layer in arcgis.com and displays the result as a list. By taking advantage of the new state syntax in Flex 4, if the user rotates the phone to view the info in landscape mode, the list is replaced with a map showing the selected states. Cool ?
There is no new Flex API for ArcGIS to learn. I'm using the same old Query, QueryTask, Map, ArcGISTiledLayer, GraphicLayer and SimpleFillSymbol classes from the released Flex API.
Now, to compile the application using Flash Builder Burrito, make sure to include in the libs project folder the flexapi swc and make sure to copy from the Flex sdks folder (under the burrito application folder) the mx.swc and the sparkskins.swc files. For the next release of our swc will not require the last two swc, just add them for now. Your package explorer in Flash Builder should look like this:

Now you can compile, deploy and run your swf on the USB attached Android. Make sure that you have the latest AIR runtime on your phone. If not, then delete AIR using the Settings view and redeploy the application, the latest runtime engine will be automagically installed.

Now to the iPhone. Currently, Burrito does not have a built-in wizard to postprocess the swf into and an ipa. This must be done via command line tools. So, download and install the latest iphone packager for your platform (windows or mac). In my case, I had to re-compile the source code to reproduce the swf. Here is the content of my bash shell script:

FLEX_HOME="/Applications/Adobe Flash Builder Burrito/sdks/4.5.0"
"${FLEX_HOME}/bin/mxmlc"\
-load-config "${FLEX_HOME}/frameworks/airmobile-config.xml"\
-compiler.include-libraries+=libs\
-sp src\
-o bin-debug/MyMobileApp.swf src/MyMobileApp.mxml


Next is to convert the swf to an ipa. Here is the content of my shell script:

~/packagerforiphone_v2_mac_101110/bin/pfi\
-package\
-target ipa-test\
-provisioning-profile ~/YourProfile.mobileprovision\
-storetype pkcs12\
-keystore ~/YourFile.p12\
-storepass YourPassword\
MyMobileApp.ipa\
bin-debug/MyMobileApp-ios.xml\
bin-debug/MyMobileApp.swf\
bin-debug/assets/Default.png\
bin-debug/assets/Icon29.png\
bin-debug/assets/Icon57.png\
bin-debug/assets/Icon512.png


Here is the content of MyMobileApp-ios.xml:

<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://ns.adobe.com/air/application/2.0">
<id>ThunderHead</id>
<filename>MyMobileApp</filename>
<name>MyMobileApp</name>
<version>v1</version>
<initialWindow>
<renderMode>gpu</renderMode>
<content>bin-debug/MyMobileApp.swf</content>
<fullScreen>true</fullScreen>
<aspectRatio>portrait</aspectRatio>
<autoOrients>true</autoOrients>
</initialWindow>
<supportedProfiles>mobileDevice</supportedProfiles>
<icon>
<image29x29>bin-debug/assets/Icon29.png</image29x29>
<image57x57>bin-debug/assets/Icon57.png</image57x57>
<image512x512>bin-debug/assets/Icon512.png</image512x512>
</icon>
<iPhone>
<InfoAdditions><![CDATA[
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleBlackOpaque</string>
<key>UIRequiresPersistentWiFi</key>
<string>NO</string>
]]></InfoAdditions>
</iPhone>
</application>


Again - if you want to know what all the above options mean - you _must_ read the packager documentation. The pfi command will run for a long time - do not worry - it will eventually finish - again, this is still in its early stages, and I hope that Adobe will make this easier and faster in the future.
Now drag the ipa file onto iTunes and sync your iPhone. you should in theory have the app on your iPhone.
I hope this works for you and tell me what you think.

33 comments:

Greg Knight said...

Dude, u r one crazy mo-fo! Love it!

Peter said...

So cool... they need the iphone export wizard - i RTFM'd the iphone packager, and seems like a major pain. Was it very difficult? Looks quite involved... This could be huge!!

Any chance you can post more detail on the process of porting to ipa? All the doc assumes using Flash CS5, but i am using Flash Builder "Burrito" - assume that is OK?

Peter said...

Also, i could not import the sample code zip into Burrito, said was not a valid project. Tried both pointing to the zip and to the folder of the unzipped contents. Any thoughts?

_ said...
This comment has been removed by the author.
Yuletide said...

you are my hero

mhoyland said...

Can you use the phone's GPS to get the current location? That would provide a better opportunity for smarter apps.

thunderhead said...

Sure u can - there is a full GeoLocation API in flex

Darren said...

Fantastic! I was able to get it work on my development iPhone2G. Had some problems with Hero not running exactly as it should...and, of course, no hardware "back" button.

Other then that, it does run, albeit slow.

GREAT Job and I do see what is possible, and I believe Adobe will definitely improve this one.

Javier Abadía said...

hi...

i'm getting this error when packing...

Exception in thread "main" java.lang.Error: Unable to find named traits: mx.collections.errors::SortError
at adobe.abc.Domain.resolveTypeName(Domain.java:195)
at adobe.abc.Domain.resolveTypeName(Domain.java:112)
at adobe.abc.GlobalOptimizer$InputAbc.resolveTypeName(GlobalOptimizer.java:274)
at adobe.abc.GlobalOptimizer$InputAbc.readCode(GlobalOptimizer.java:983)
at adobe.abc.GlobalOptimizer$InputAbc.readBody(GlobalOptimizer.java:531)
at adobe.abc.GlobalOptimizer$InputAbc.readAbc(GlobalOptimizer.java:404)
at adobe.abc.GlobalOptimizer$InputAbc.readAbc(GlobalOptimizer.java:280)
at adobe.abc.LLVMEmitter.generateBitcode(LLVMEmitter.java:160)
at com.adobe.air.ipa.AOTCompiler.convertAbcToLlvmBitcode(AOTCompiler.java:329)
at com.adobe.air.ipa.AOTCompiler.GenerateMacBinary(AOTCompiler.java:600)
at com.adobe.air.ipa.IPAOutputStream.compileRootSwf(IPAOutputStream.java:196)
at com.adobe.air.ipa.IPAOutputStream.finalizeSig(IPAOutputStream.java:366)
at com.adobe.air.ADTPackager.createPackage(ADTPackager.java:65)
at com.adobe.air.ipa.IPAPackager.createPackage(IPAPackager.java:165)
at com.adobe.air.ADTEntrypoint.parseArgsAndGo(ADTEntrypoint.java:132)
at com.adobe.air.ipa.PFI.parseArgsAndGo(PFI.java:152)
at com.adobe.air.ADTEntrypoint.run(ADTEntrypoint.java:68)
at com.adobe.air.ipa.PFI.main(PFI.java:112)


any hints?

CheckMate808 said...

Another cool blog. Right On!
can you post some images?

Unknown said...

Thanks for the great tutorial. However, i am having an issue compiling with mxmlc. This is my build swf cmd:
"c:\Program Files\Adobe\Adobe Flash Builder Burrito\sdks\4.5.0\bin\mxmlc" -load-config "c:\Program Files\Adobe\Adobe Flash Builder Burrito\sdks\4.5.0\frameworks\airmobile-config.xml" -library-path -sp -o MyMobileApp.swf "c:\Users\Morgan\Adobe Flash Builder Burrito Preview\MyMobileApp\src\MyMobileApp.mxml"

I get an error saying "Error: Could not resolve to a component implement" or an error regarding compile time constants. This sounds like my libraries are not being included in the build. I would greatly appreciate any feedback anyone may have regarding this issue. And yes, i am new to mxmlc compiling, so please forgive me if the answer is obvious. Thanks again,

Morgan
GeoSurf, Inc

thunderhead said...

Sorry for the delay - Looks like your library path is missing the swcs.

phile13 said...

So I tried your code today with 2.3 and got this error, any thoughts?

VerifyError: Error #1014: Class mx.controls.sliderClasses::Slider could not be found.

at com.esri.ags::Map()[C:\checkout\flex_api2\api\src\com\esri\ags\Map.as:526]
at views::FeatureList/_FeatureList_Map1_i()
at mx.core::DeferredInstanceFromFunction/getInstance()[E:\dev\hero_private_beta\frameworks\projects\framework\src\mx\core\DeferredInstanceFromFunction.as:105]
at mx.states::AddItems/createInstance()[E:\dev\hero_private_beta\frameworks\projects\framework\src\mx\states\AddItems.as:487]
at mx.states::AddItems/initialize()[E:\dev\hero_private_beta\frameworks\projects\framework\src\mx\states\AddItems.as:502]
at mx.states::State/http://www.adobe.com/2006/flex/mx/internal::initialize()[E:\dev\hero_private_beta\frameworks\projects\framework\src\mx\states\State.as:257]
at mx.core::UIComponent/initializeState()[E:\dev\hero_private_beta\frameworks\projects\framework\src\mx\core\UIComponent.as:10608]
at mx.core::UIComponent/commitCurrentState()[E:\dev\hero_private_beta\frameworks\projects\framework\src\mx\core\UIComponent.as:10306]
at mx.core::UIComponent/commitProperties()[E:\dev\hero_private_beta\frameworks\projects\framework\src\mx\core\UIComponent.as:8180]
at spark.components.supportClasses::GroupBase/commitProperties()[E:\dev\hero_private_beta\frameworks\projects\spark\src\spark\components\supportClasses\GroupBase.as:1089]
at spark.components::Group/commitProperties()[E:\dev\hero_private_beta\frameworks\projects\spark\src\spark\components\Group.as:826]
at mx.core::UIComponent/validateProperties()[E:\dev\hero_private_beta\frameworks\projects\framework\src\mx\core\UIComponent.as:8095]
at spark.components::ViewNavigator/createViewInstance()[E:\dev\hero_private_beta\frameworks\projects\mobilecomponents\src\spark\components\ViewNavigator.as:1738]
at spark.components::ViewNavigator/commitViewChange()[E:\dev\hero_private_beta\frameworks\projects\mobilecomponents\src\spark\components\ViewNavigator.as:1661]
at spark.components::ViewNavigator/commitProperties()[E:\dev\hero_private_beta\frameworks\projects\mobilecomponents\src\spark\components\ViewNavigator.as:1063]
at mx.core::UIComponent/validateProperties()[E:\dev\hero_private_beta\frameworks\projects\framework\src\mx\core\UIComponent.as:8095]
at mx.managers::LayoutManager/validateClient()[E:\dev\hero_private_beta\frameworks\projects\framework\src\mx\managers\LayoutManager.as:934]
at mx.core::UIComponent/validateNow()[E:\dev\hero_private_beta\frameworks\projects\framework\src\mx\core\UIComponent.as:7953]
at spark.components::ViewNavigator/executeDelayedActions()[E:\dev\hero_private_beta\frameworks\projects\mobilecomponents\src\spark\components\ViewNavigator.as:1170]

Peter said...

You have to add references in your flex project to adobe's mx.core libraries. In Flash Burrito, it only loads the spark stuff, and the ESRI API still uses mx stuff.

phile13 said...

Are you saying that I need to add a source path to the 3.5 sdk so that it can find the missing components code. or do you have a different method in mind.

thunderhead said...

No - just add the mx.swc and sparkskin.swc to ur libs folder.

Unknown said...

I had this code working on flex builder burrito, updated to the new version flash builder 4.5 and am getting no errors when compiling. But when running the app and executing a query I get this error
TypeError: Error #1009: Cannot access a property or method of a null object reference.
at views::FeatureList/set data()[C:\Users\anash\Adobe Flash Builder 4.5\VernonGIS_Mobile\VernonGIS\src\views\FeatureList.mxml:17]
at spark.components::ViewNavigator/createViewInstance()[E:\dev\hero_private\frameworks\projects\mobilecomponents\src\spark\components\ViewNavigator.as:1928]
at spark.components::ViewNavigator/commitNavigatorAction()[E:\dev\hero_private\frameworks\projects\mobilecomponents\src\spark\components\ViewNavigator.as:1849]
at spark.components::ViewNavigator/commitProperties()[E:\dev\hero_private\frameworks\projects\mobilecomponents\src\spark\components\ViewNavigator.as:1220]
at mx.core::UIComponent/validateProperties()[E:\dev\hero_private\frameworks\projects\framework\src\mx\core\UIComponent.as:8209]
at mx.managers::LayoutManager/validateProperties()[E:\dev\hero_private\frameworks\projects\framework\src\mx\managers\LayoutManager.as:597]
at mx.managers::LayoutManager/doPhasedInstantiation()[E:\dev\hero_private\frameworks\projects\framework\src\mx\managers\LayoutManager.as:813]
at mx.managers::LayoutManager/doPhasedInstantiationCallback()[E:\dev\hero_private\frameworks\projects\framework\src\mx\managers\LayoutManager.as:1180]

I added in the two swc files that are missing. Any help would be appreciated. I have been trying to resolve this for a week now

thunderhead said...

try this project - http://dl.dropbox.com/u/2193160/MobileApp.fxp

thunderhead said...

Save the link as a file - do not open it directly specially with IE.

Unknown said...

thanks for your help your solution worked

Jeremy said...

Hey all,

Trying to implement this using the 4.5 release and keep getting this error. I have sorted out that it fails when trying to bind dataprovider to the list in this line of code:

list.dataProvider = new ArrayList(featureSet.attributes);

I can see the objects in debug mode, but it doesn't want to bind the attributes for some reason.

Any ideas?



TypeError: Error #1009: Cannot access a property or method of a null object reference.
at views::FeatureList/set data()[/Users/-----/Documents/Adobe Flash Builder 4.5/TestMobile/src/views/FeatureList.mxml:30]
at spark.components::ViewNavigator/createViewInstance()[E:\dev\hero_private\frameworks\projects\mobilecomponents\src\spark\components\ViewNavigator.as:1928]
at spark.components::ViewNavigator/commitNavigatorAction()[E:\dev\hero_private\frameworks\projects\mobilecomponents\src\spark\components\ViewNavigator.as:1849]
at spark.components::ViewNavigator/commitProperties()[E:\dev\hero_private\frameworks\projects\mobilecomponents\src\spark\components\ViewNavigator.as:1220]
at mx.core::UIComponent/validateProperties()[E:\dev\hero_private\frameworks\projects\framework\src\mx\core\UIComponent.as:8209]
at mx.managers::LayoutManager/validateProperties()[E:\dev\hero_private\frameworks\projects\framework\src\mx\managers\LayoutManager.as:597]
at mx.managers::LayoutManager/doPhasedInstantiation()[E:\dev\hero_private\frameworks\projects\framework\src\mx\managers\LayoutManager.as:813]
at mx.managers::LayoutManager/doPhasedInstantiationCallback()[E:\dev\hero_private\frameworks\projects\framework\src\mx\managers\LayoutManager.as:1180]

thunderhead said...

Download latest src from http://dl.dropbox.com/u/2193160/MobileApp.fxp

arathorn_tr said...

Hi, is there anyway to get my map offline with using esri flex api. As far as i know, esri ios api support something like that with using map caching?? Thankss...

thunderhead said...

There is an update to this post - http://www.adobe.com/devnet/flash-builder/articles/mobile-mapping-app-arcgis.html

thunderhead said...

There is not yet an COTS offline caching solution

sunday said...

Thanks for the ideas!!

Do you find that performance is shot by the adobe framework?

Why does my 4mb (200 lines of code) swf turn into a 23 mb app? (with esri flex, mx and sparkskins swc's)

thunderhead said...

Getting better with each release of AIR - tested up tp 2.6 - not 2.7 yet - and yes, in the version that I posted, it is because of the mx dependency - which we fixed in the soon to be released 2.4 API.

arhammersmith said...

Thanks, again! Any thoughts on persisting the whereInput's text property? It always gets reset to the default, but I want it to show the last search string. I tried removing the intermediate QueryExecuteView to try using popView() (instead of popToFirstView()) on the QueryResultView's Home button, but no luck. I thought each view's data property was supposed to be maintained in the stack. Well, if it is, how do I use it? Thanks!

arhammersmith said...

Found it!
from here
Although I did *not* have to set my destructionPolicy='never'.
FWIW... I just had to manually set:
data.text = whereInput.text;
right before pushing the QueryResultView. Then when I use navigator.popView() to go back, my last search string is there. I also moved the BusyIndicator into the QueryView and toggle it's visibility with executing/handling the query/response.

Unknown said...

is it possible to test your sample application over an emulator? or we need a device for that?

thunderhead said...

FlashBuilder 4.6 comes with Emulator !

undefined said...

Hi Mansour,

thanks for the hack! I have a question though.

Esri docs at http://resources.arcgis.com/en/help/flex-viewer/concepts/index.html#/FAQs/01m300000004000000/ state that:

===================
Is it possible to run the ArcGIS Viewer for Flex application on my iPhone or iPad?

No. There are two specific issues related to this.
The ArcGIS Viewer for Flex is a standard Flex project, it would need to be re-written as a Flex Mobile Project.
The ArcGIS Viewer for Flex uses Flex modules. These are not supported in Flex mobile projects.
===================

What's up with that, is it true that to use Esri Flex on iOS, one needs to ditch the viewer?

thunderhead said...

Actually this has nothing to do flex - iOS does not allow any application to be modular - that means extra executable code cannot be downloaded after the fact - so yes - you will have to rewrite the app - some people have done this as they will have to rewrite it to create a better mobile experience.