Introduction

"And soon I will have understanding of videocassette recorders and car telephones. And when I have understanding of them, I shall have understanding of computers. And when I have understanding of computers, I shall be the Supreme Being!" (Time Bandits 1981)

Chris Ellis is a freelance Senior Flash Programmer, former Lingo Programmer and resident of London.

He has an MSc in Interactive Multimedia from Westminster University, graduating in 2000. He started programming at the age of 8 with a BBC Model B and a few books on BASIC.

What follows are some snippets from what he's currently up to.

Wednesday, 28 December 2011

Flex Mobile In Practice

So I need to make an a mobile app... should I build it natively twice (I can do a bit of Java dev, but would have to learn xcode a bit better for this), build it in HTML (not sure how jazzy it's going to look) or try out Flex mobile (should be easy for an AS3 developer right?! And it's going to work on a Blackberry Playbook without any extra work!).

So, I have spent a few weeks exploring the pros and cons of Flex mobile development. There are a number of decent tutorials that are out there, but I found that I still had to work out a lot of stuff for myself (like in the olden days), so I decided to note down a few things that I have learnt that may save someone a bit of searching.

This is written for experienced AS3 developers. The things I picked up are from my own research and inevitably a bit of searching on the web.

I usually don't work in Flex, just pure AS3. I decided to go down the Flex route for this though, mainly to save time as I didn't want to build common components from scratch and that Adobe are pushing 'Flex Mobile', which comes with a few nice inclusions such as (amongst other things) simple navigation and data storage.

The first thing I learnt was that in actual fact, although some components are, not all Flex components are actually 'mobile ready' at the time of writing. The list of ones that are ready, which should be kept up to date can be found here:

http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/spark/skins/mobile/package-detail.html

In the end I used Flash Builder 4.6 as it worked with Flex 4.6 out of the bag. I usually use FDT, but Flash Builder seems a bit better at dealing with MXML for the uninitiated, as it's always possible to switch to design view. There was also an issue whereby I couldn't get FDT debug to run on my device either, but it seemed to work ok from Flash Builder and I didn't have the patience to work out why.

The devices I was testing on were an HTC Incredible S and a Sony S Tablet. Setting up the Android SDK and getting them recognised by the ADT was pretty straight forward and there are lots of tutorials that will help you do this. The only minor pain was finding drivers for the devices, but this was just a case of googling around a bit.

Switching between layouts

When making any mobile app, effectively you are making an app that needs to look good in three resolutions (160,240 and 320 dpi) all of which run in two layouts (horizontal and vertical). So that's 6 different layouts per app!
From reading around, it seems that the best thing to do is to make the app for the smallest target dpi (160) and scale up. Bitmaps are handled slightly differently but I'll look at that later. By declaring the application dpi in the application's mxml constructor (not sure the correct name for this in Flex) as such:

applicationDPI="160"

Flex will then scale everything up automatically for the bigger dpi's.
One of the drawbacks of this are that it's probably not desirable for bitmaps to be scaled up. This is easily remedied by using the mutli-dpi source bitmap property of a Flex Image component.


<s:image id="myImage">
<s:source>
<s:multidpibitmapsource
source160dpi="assets/low-res/my-image.jpg"
source240dpi="assets/med-res/my-image.jpg"
source320dpi="assets/high-res/my-image.jpg">
</s:multidpibitmapsource>
</s:source>
</s:image>


All three images get packaged up into your application, but the one closest to the target applications dpi will be used. Simple.

One thing that I wanted to do was to be able to change the actual layout when there were different dpi's and different orientations. The 'cleanest' way seemed to be through CSS. It took a bit of messing around but eventually I worked out how to do this.

For orientations, it was just a case of listening to the onResize event in the main application and then check whether the width was more than the height or visa versa. This would tell me whether the app was in landscape or portrait.

So, in the application's mxml constructor I defined resize function:
resize="resizeHandler(event)"

defined the possible orientation states:

<s:states>
<s:state name="portraitPhone" stategroups="phone,portrait">
<s:state name="landscapePhone" stategroups="landscape,phone">
<s:state name="portraitTablet" stategroups="portrait,tablet">
<s:state name="landscapeTablet" stategroups="landscape,tablet">
</s:state>

and defined the resize handler, which would work out what the orientation and device type was.


protected function resizeHandler(event:ResizeEvent):void
{
var isPortrait:Boolean = height > width;
//determining if its a tablet worked ok with the hardware I tested, but this is not a water tight method.
isTablet = height > 960 || width > 960;

var orientation:String = (isPortrait ? "portrait" : "landscape");
var device:String = (isTablet ? "Tablet" : "Phone");

currentState = orientation + device;
}


Ok, so that's pretty easy.

The CSS in Flex is pretty nifty and can be written in such a way as to change automatically depending on the state of the application. This makes it easy to deal with the orientations. The example I am about to give is not that great. It shows what I am talking about, but once I had it sussed I found out that Form components were not actually mobile ready! Still, the syntax is the same. It's possible to change properties of classes as such:

s|Application:portraitPhone s|FormItem #labelDisplay
{
text-align: left ;
}

s|Application:landscapePhone s|FormItem #labelDisplay
{
text-align: right ;
}


and also the skins of componenets as such:

s|Application:portraitPhone s|FormItem
{
skinClass: ClassReference("spark.skins.spark.StackedFormItemSkin");
}

s|Application:landscapePhone s|FormItem
{
skinClass: ClassReference("spark.skins.spark.FormItemSkin");
}


So, as I was changing the state dependant on orientation in the main Application class, the syntax is:

s|Application:stateName

When the main application changes state the Forms label display should switch from left to right. There was a few more bits of CSS to get the form changing but as I mentioned above, at the moment, you shouldn't be using Forms components in mobile apps, so I won't bother to show the complete example.

Now to get the componenets to update their CSS and redraw at runtime, you will need to add the following line to the end of the resize handler:

styleManager.styleDeclarationsChanged();

I have no idea how fast or slow this is in comparison to other methods people may have, or whether there is a better solution, but the method above seems clean and can be used in conjuction with dpi declarations by nesting them in a @media tag (this is not something I got round to testing mind). It would look something like this:

/* landscape @ 240dpi */
@media (application-dpi: 240) {
s|Application:landscapePhone s|FormItem
{
skinClass: ClassReference("spark.skins.spark.FormItemSkin");
}
}


So, that is the basics of dealing with the different layouts on Flex mobile.

Loading in local HTML files

One thing I wanted to do, as lots of apps will, is to use Google Maps. This is very simple and looks great using native Android programming, but I found that the same is not true in Flex. In fact I would go as far as to say that if you want to use Google Maps in your app then don't use Flex. It sucks.

As the Flash API is now deprecated from Google Maps (and even if you do use it, it won't work on Apple devices), the answer is to use the Javascript API. Hmmmm.... There were a number of things I needed to do such as placing down markers that the user could then move around so i figured I needed some JS functions to achieve this. No problem, I would just use a static HTML page, put all the JS I needed into that and load that into a stageWebView.

Displaying local HTML pages in a Flex mobile app and communicating with it via JavaScript was not as straightforward as I had hoped. After a lot of looking around, experimenting, laughing, crying and cups of tea I ended up using this incredibly useful set of classes called the stageWebViewBridge. The details of this project can be found here:


http://code.google.com/p/stagewebviewbridge/


Essentially it simplifies the process of loading in local files and communicating via JavaScript. The wiki entry regarding loading the files was still a bit confusing if using Flash Builder as all the examples are made in Flash CS 5.5 (yes some serious programmer is still using Flash!). The main confusion for myself was working out where to put the HTML files on my machine and how to reference that location from Flash Builder so I will try and clear that up a bit.

When running the app from Flash Builder on a device all you need to do is to create a folder called "www" in the source folder (by default src). This automatically gets copied across to the bin-debug folder by Felx and can then be loaded using:

webView.loadLocalURL("applink:/myHTMLpage.html");


When running using the emulator, things seemed a tad more odd. I had to put the files in:

C:\Users\UserName\AppData\Roaming\LocationAlarm.debug\Local Store\wwwSource

Which got copied across to

C:\Users\UserName\AppData\Roaming\LocationAlarm.debug\Local Store\www

at runtime by the bridge. To load the file was exactly the same as for using a device.

Communication to and from JavaScript was very simple and needs no explanation. Just read the stageWebViewBridge wiki on the link above.

One issue I did have was due to the auto-scaling feature of Flash mobile that I was using. The viewport for the StageWebView needs to have it's size set in pixels. I was using a TabbedViewNavigatorApplication as my main class and wanted the web view to sit inside this. The StageWebView reminds me a bit of the 'directToStage' video in Director (remember that?) in that it sits above all content above the actual application. So, it needs to be the right size. My tabbed navigator was using auto-scaling though, so I had to work out how many pixels big the StageWebView needed to be for the current device. I couldn't find an elegant solution for this but I did find a solution. As discussed here you need to scale it yourself:

http://stackoverflow.com/questions/6759547/air-stagewebview-on-iphone

So, I needed to get the size of the screen inbetween the (scaled) top bar and the (scaled) tabs. This is how I did that:

//firstly get the current scale factor
var currentFlexScalingFactor:Number=FlexGlobals.topLevelApplication.runtimeDPI/FlexGlobals.topLevelApplication.applicationDPI;
//then get the size of the screen minus the scaled sizes of the top bar and the bottom nav buttons
webView.viewPort=new Rectangle(0, systemManager.screen.height*currentFlexScalingFactor-height*currentFlexScalingFactor-FlexGlobals.topLevelApplication.tabbedNavigator.tabBar.height*currentFlexScalingFactor,width*currentFlexScalingFactor,height*currentFlexScalingFactor);

Not very pretty, but it works.
The code to size the viewport needs to be called when the device's orientation changes as well. This will not happen automatically.

So, ultimately I got my map working, but then came across a problem whereby the actual web page would zoom in and out when using pinch/reverse pinch gestures. So the map controls then get massive when zooming in and out, rather than just the map. I found the following "fix" for this. In the HTML page (which I created) just add the following meta tag:

<meta name="viewport" content="target-densitydpi=device-dpi, width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">


Simple? Well it would be if it worked. Unfortunately HTC have their own flavour of browser which doesn't support the viewport meta tag. This means that on virtually all HTC devices (I believe there may be one exception) the stageWebView is not a good user experience.

I guess (although am yet to test) that this means HTML based apps will suffer the same problems on HTC devices. If so, they're not an option for commercial apps targeting Android. HTC has distributed way too many devices to be ignored.

Next up was storing data locally. This was easy enough. I ended up using a SQLlite database. This is done the same as it would be in any AIR application. There are great tutorials all over the web on this so I will not bother to explain how this is done.

There is an 'auto-save' type function alos, to save small bits of data when switching between views. Essentially, you just turn it on in the main navigation constructor by setting:

persistNavigatorState="true"


As far as I could tell this is a shortcut to creating shared objects which you save and load using:

//set value
persistenceManager.setProperty("someProp","myProperty");
//get value
persistenceManager.getProperty("someProp");


A more detailed discussion of how this works and other ways of working with it can be found here:

http://devgirl.org/2011/05/18/flex-4-5-mobile-data-handling/

By this point I had come to the conclusion that Flex Mobile was not good at handling any of the 'phone' features that may need to be intergrated. I had a brief look at using Native Libraries to get the phone to vibrate but it isn't a simple procedure. When making a native android app it's two lines of code!

0 comments: