Primer on Flex/AIR Multiscreen Development
This primer addresses the development of Flex/AIR applications that target phones, tablets, browser and desktop. It contains a brief introduction to multiscreen development, a description of multiscreen dev issues you'll encounter, and some tips for dealing with these issues.
Note that this assumes a good working knowledge of Flex and AIR and a basic knowledge of AIR mobile development. Also, note that this is just a primer, so it doesn't get too deep into the weeds (though it does include references to many good in-depth resources).
Use these links to jump to a topic:
Multiscreen Intro
In multiscreen development the goal is to deliver your application's functionality on multiple targets, usually some combination of phone, tablet, desktop and browser (though a multiscreen application can target only mobile screens, i.e., different classes of phones and tablets).
Multiscreen applications have to deal with different OS's, UI conventions and device capabilities, but dealing with the proliferation of screen sizes and pixel densities takes up a lot of your time and often drives your design. Radley Marx has a nice bit on this in his post "Multiscreen development means multiple screens, not all-in-one":"Multiscreen is now about taking a single application and developing it for multiple interfaces — one for each screen type: smartphone, tablet, desktop, and television. It's not an all-in-one app, it's one app with many views. The basic app is packaged and released with templates and additional features specific to each screen. The final result can be a single app with multiple views... or many apps built from the same base app."
So, a game that is multiscreen would have core functionality (and core code) shared by versions that run on different targets and whose UI's are always appropriate to their target. The wide range of screen sizes/dpi's and UI conventions you'll encounter means that your multiscreen application's UI will vary a bit (sometimes more than a bit) between its target-specific versions. For example, the small-screen phone version might display less status information and its user would move game pieces with their finger instead of a mouse. Ultimately, though, the multiscreen goal is to have this game play pretty much the same wherever it's run. And with the right design you should be able to do this with a lot of code reuse — maybe not so much with the presentation layer, but most everything else should be sharable.
As for how you might develop a multiscreen application using Flex and AIR, here's a nice bit from Cantrell's article "Writing multiscreen AIR apps":
"There are a several different techniques for developing multiscreen applications. A few that I considered are:
- Architect your application in such a way that device-specific presentation layers can be placed on top of your core application logic.
- Develop components and libraries that can be reused in as many environments as possible.
- Develop a single code base that can automatically adapt to any environment.
I think the first two options are what most developers will find work best for them. Rather than writing a single application that gets deployed everywhere, the goal would be to reuse as much code as possible, and therefore minimize the amount of device-specific code that needs to be written."
Cantrell has it right when he says that the first two approaches work best, at least for Flex/AIR multiscreen development. Basically you are creating a logical application composed of target-specific versions that share a lot of code. To do this you:
- leverage Flex/AIR's ability to run on many platforms/OS's, whenever possible creating views and ActionScript routines that are sharable across your targets
- handle differences in your target screens through things like CSS (to style your UI to platform conventions and to wire up your skins) and Flex features like skinning and AIR mobile features like application scaling (which can adjust sizes and layouts to accomodate different dpi's)
- allow for target-specific code to take advantage of target-specific capabilities (e.g., GPS on a phone) using basic programming techniques such as conditionals on availability properties (e.g.,
if (Geolocation.isSupported)
) and occasionally using conditionals on OS (e.g.,
var isAndroid:Boolean=Capabilities.version.indexOf('AND') > -1;
) - use different Flex/AIR compiler options (e.g., use of -theme, use of AIR's application descriptor XML to specify your Android permissions, etc.) to generate the output required for your different targets (e.g., for Android an .apk file, for browser a .swf and usually some related html and js files, etc.)
Below is a crude representation of how you might implement this using Flex and AIR: shared code libraries (.swc's) are combined with target-specific shell projects which are run through the compiler to generate target-specific versions of your multiscreen app.
The above is just one possible approach — you could have different output (e.g., Playbook .bar) and different input (e.g., use multiple swc's, perhaps a core.swc for things like your data model code, common events, enums, etc. while using a sharedViews.swc for views you'll be sharing across targets).
Multiscreen Issues
When creating applications that target multiple "screens" (devices, form factors, OS's, UI conventions, etc.) you'll encounter numerous issues that affect how you write your code — which components you can use, your architecture within Flash Builder, how you style and skin your UI, etc. Multiscreen applications that include both mobile and non-mobile targets will encounter more of these issues than multiscreen applications that target only mobile.
I'll use the term mobile to refer to both phone and tablet, but keep in mind there are some big differences between these two — not just screen size and dpi but also horsepower, since tablets have more memory than most phones and are more likely to have dual CPUs. |
Some multiscreen dev issues arise from the differences between mobile and non-mobile and some from the heterogeneity of mobile itself. Examples: non-mobile can use rollover events and toolTips but mouse-less mobile cannot; the wide variety of phone screens means you'll need components/views that adapt to different pixel densities; desktops don't have accelerometers.
You'll also encounter issues unique to Flex/AIR multiscreen projects. One significant issue is the components you can use. A Flex application targeted to the browser can use the Halo (mx:) and Spark (s:) components but can't use AIR components like BusyIndicator (worse, can't use any AIR classes). On the other hand, AIR mobile apps can't use Halo components (exception: charting components) and can't use many Spark components (and some you can use incur significant performance and memory costs on mobile devices).
There are also issues specific to AIR mobile apps. For example, an AIR app running on Android can dynamically load .swf's while one running on iOS cannot.
And there are Flash Builder issues: how should you physically organize your code and link it together to execute builds that generate the runtime files required for your different targets? For example, your browser version needs different compiler options than your mobile targets. And what about all those output files you'll need to generate? For Android you'll create a single .apk but that browser-based version can create an output directory with many files, not just your swf but also an HTML wrapper and subdirectories with assets.
Here are some of the issues you'll encounter when creating multiscreen apps (note how many are driven by mobile):
- views and components used in a multiscreen app generally need to account for different screen sizes and pixel densities. They also need to adjust to different capabilities (a touch-based UI versus mouse-driven) and user interface conventions (scrollbars required for desktop but not for swipe-to-scroll mobile) and styling conventions (Apple buttons have a beveled appearance but Android buttons don't)
- unlike AIR mobile apps, a Flex browser-based application can't use AIR classes and has to play in the browser's security sandbox
- views you'll use in mobile projects have far fewer Flex framework components available to them than do non-mobile views
- views for mobile need dynamic layout to handle screen rotation (unless your app doesn't honor rotation, but even then it still needs to adapt to different resolutions)
- mobile's higher dpi and touch-based interfaces requires larger controls — your fat finger needs a bigger hit point than does a mouse cursor. And these larger controls eat up mobile's precious screen real estate — a Droid X's 480x854 is even smaller than it initially sounds
- mobile has less horsepower than desktop — code that's performant on a desktop machine might crawl on a phone. More efficient code also eats less battery.
- while a large-screen desktop might display several views at a time and pass data between them via events, an AIR phone app that uses ViewNavigator has a single view in memory at a time and generally needs a different data-passing mechanism
- a phone's connection can be slow, is sometimes intermittent, and may incur bandwidth usage charges. This gives local data caching a higher priority in multiscreen design than it generally has in non-mobile design
- saving and restoring application state is a nice-to-have feature for desktop and browser applications but a multiscreen application that includes phone targets usually requires maintenance of state since a phone can kill your app at any time for memory management or when receiving a call
This list could be much longer. Here are some docs/posts/videos with more in-depth content on mobile and multiscreen:
- Mobile is a primary multiscreen target, so Adobe's main developer doc for mobile dev is indispensible: "Developing Mobile Applications with ADOBE FLEX and ADOBE FLASH BUILDER". Here is the PDF for Flex 4.6 sdk (100+ pages).
- "Differences in mobile, desktop, and browser application development" is a brief intro to some basics
- Narciso Jaramillo did a 2011 360Max presentation on multiscreen issues, Here is the video and here are his slides
- Dylan dePass has a Max 2011 presentation "Putting the Multi in Multiscreen" on Adobe TV.
- Adobe has some 4.5 multiscreen reference apps including SalesDashboard with a supporting doc here
Topic-specific references appear throughout this primer, including numerous references to content from Adobe employees Narciso Jaramillo and Jason San Jose.
The remainder of this doc focuses on the issues described above and offers tips/suggestions for dealing with them. Since screens are a huge issue in multiscreen dev they're the place to start.
Dealing with Screens
Here's another good bit from Cantrell, this time from "Authoring mobile Flash content for multiple screen sizes" (this post is from early 2010, the "Slider" days of Flex/AIR mobile dev).
"The goal of a multi-screen application is not necessarily to look identical on every device; rather, it should adapt to any device it's installed on. In other words, multi-screen applications should dynamically adjust to the resolution and the PPI of their host devices, displaying more information on larger screens, removing or shrinking elements on smaller screens, ensuring buttons are physically large enough to tap on, and so on. In order for applications to work across different screens, they must be architected in such a way that they draw and redraw themselves at the proper times and using the proper constraints."
So, a multiscreen application is a bit of a chameleon, adjusting appearance to fit the situation. Whenever possible you'd like to achieve this with views that work across all target screens, but given the variety of screen sizes/resolutions/dpi's this generally isn't feasible. Of course you can sometimes get simple views to work for multiple screens through adaptive design. More often you'll create different "faces" for different targets, usually using Flex skins. For example, you can create two skins for a component, one for mobile and another for non-mobile. On occasion you'll create and inject target-specific views. Here are some of the techniques you use in multiscreen dev (note that some of these items overlap, and you'll generally use a combination of these):
- use of Flex 4 (Spark) skinning: with skinning you segment a component into two classes, one to encapsulate its logic/data and the other its UI — its skin. By applying different skins to your component you provide different user interfaces to your component's functionality. Often you'll create both a mobile and non-mobile skin for a component and apply the appropriate skin at compile-time or even based on runtime criteria.
- adaptive design (layout): a view can sometimes be used across multiple (even all) targets by dynamically adjusting component coordinates, sizes, spacing, even visibility based on the available screen real estate. For this you use the same sizing/layout features you use in browser-based applications to handle browser resizing — things like layout classes (e.g., VerticalLayout), constraints (e.g., bottom=10), percentage-based sizing (e.g., percentWidth=100), responding to events (e.g., resizeEvent), etc. You'll also use dynamic layout to handle orientation changes on mobile.
- adaptive design (UI conventions): with CSS you can do a lot to style your UI to OS conventions. One line of CSS can give all of your buttons that iOS beveled look. On occasion you'll also use ActionScript to deal with differing UI standards. An example of this is a Back button on mobile devices: your app won't need one on Android, since that's built into the device, but there's no Back button for iOS devices. A common solution is to put a Back button in the navigationContent area of your mobile views and then hide/unhide that button based on the current OS (i.e., the button is always there, but it's hidden when running on Android).
- use of Flex 4 (Spark) states: this is basically more adaptive design. Using states your views can hide and unhide components, change the layout, etc. You might use states to create a view that shows all of its parts on large-screen devices but on smaller screen devices it displays the view's parts selectively, turning what was a single view into a stripped-down view or even a wizard-like view (e.g., on the phone it hides/unhides parts of the view as the user fills in a form or completes a process).
- "swappable" views: your application architecture can employ a design pattern that supports separating the UI of your views from the logic and data that drive it. This is similar to the seperation used in component skinning (and to Code Behind) but at the view level, not the component level. This allows you to create different "faces" for a view's functionality. Then, similar to skinning, you can swap in different UIs for different targets. This approach is used in Presentation Model (used in "Multiscreen development techniques with Flex and Adobe AIR") and in Model View Presenter (used in the Adobe reference apps).
- total view reuse: a non-mobile view that uses only "mobile approved" components can sometimes be used as-is on mobile by simply "wrapping" it in a Spark View class. This applies mostly to very simple views like selection lists or Help screens.
- no view reuse: for some views, especially complex views, you'll have no choice but to create device-specific versions. In a sense this is a worst-case scenario for your multiscreen app, with minimal or no code reuse for the view, but given the wide variations in target screens this isn't uncommon in multiscreen dev.
- omit functionality: you might also find cases where a view makes sense in one target environment but not another. For example, a view that displays dense scatter plots with interactive features like dataTips and drilldown might prove frustrating to use on a phone's small screen. You could argue that this is in fact our multiscreen worst-case scenario, since here you completely omit some of your app's functionality on a target
Of course, the above all have costs as well as benefits. Here are some things to keep in mind:
- a skinning approach seems to be Adobe's preference and is a nice approach to dealing with very different screens (e.g., mobile versus non-mobile). However, their use often requires creating and maintaining many skin classes. Also, mobile skins are more complex than their non-mobile counterparts, and since they're AS-based, not done through MXML, they are not compatible with Catalyst.
- using adaptive design to create views usable on multiple targets is always desirable, maximizing code reuse. However, for many views this just isn't feasible given the range of screen sizes/dpi's you'll be dealing with. Moreover, this approach can increase code complexity. For example, using states to define different screen layouts in a view sometimes results in a proliferation of states and stateGroups that can make your view's underlying code harder to read.
- if you create screen-specific views you'll need a mechanism for injecting those views on a specific target. Sometimes you can do this using the Flex framework and sometimes your design will require use of a third-party dependency injection framework like Parsley (which comes with many benefits, but if you haven't used a di/IoC framework then you'll have to climb that learning curve).
When designing views for mobile you should keep in mind that just because you can do something on mobile doesn't mean you should. Adobe recommends that you limit (and in some cases avoid) certain features on mobile for reasons of performance and battery use. This includes use of filters, use of effects, use of the RichTextArea and RichEditableTextArea classes, and basing mobile item renderers on the ItemRenderer class. In some cases Adobe provides an alternate mobile-optimized feature. For example, in place of ItemRenderer you have the mobile-optimized classes LabelItemRenderer and IconItemRenderer.
The next few sections consider screen/view issues in more detail.
Pixel density and application scaling
An obvious difference between mobile and non-mobile is screen size, but equally important is pixel density, or dots per inch (dpi). Before diving into dpi issues it's best to define some terms:
resolution | the width and height of the screen in pixels, for example 1024x768 (common for both desktop and tablet) or 480x854 (Droid X) |
dpi (ppi) |
dots per inch (pixels per inch). Also called pixel density. Determined by the combination of screen size and resolution |
dpi category | dpi's can be mapped into categories to simplify development. Common categories in mobile are 160 (low res phones and tablets), 240 (many Android devices) and 320 (iPhone) |
Pixel densities for common multiscreen targets:
- PCs/laptops: around 100 dpi
- most tablets: around 160 dpi
- phones: 3 rough categories, 160, 240, and 320 dpi
These general categories are useful but keep in mind that actual device dpi's are all over the map. Consider some tablets: iPad is 132 dpi, Galaxy Tab 10.1" is 149 dpi, and Galaxy Tab 7" is 171 dpi. For a list of some devices and their dpi this Wikipedia page is useful.
Why is dpi important? In Flex you specify explicit sizes in pixels (e.g., button width="150"). But a pixel-based size that's appropriate for one dpi isn't going to be optimal for another. Narciso Jaramillo's 360|Flex presentation has a slide that illustrates this:
Excerpt from Narciso Jaramillo's 2011 360|Flex presentation
The problem with those phone 150x40-pixel buttons above isn't just readability of the label text, it's also "clickability" — mobile's touch-based interfaces needs fairly large hit points, large enough for your fingertip (represented here by the blue circle). Obviously "clicking" on that 150px iPhone4 button could be a challenge, especially if it's nested among several other touch-enabled components.
So, components and views you'll use across different dpi's need some way of adjusting to the current dpi so your buttons are always the same size (or at least always a useable size). Continuing with the above example, on an iPhone you may not need that button to be 1.5" x .4, but you certainly can't have it .46" x .13". And now think about text size, which you also specify in pixels. If you set text to 16px it's readable on a tablet, but when it's displayed on a 240dpi Droid you'll need reading glasses, and for the iPhone you'll need a magnifying glass. Of course, there are ways to deal with this "manually", setting sizes based on runtime dpi. But that can be a lot of work. What you really want is to set sizes and layout appropriate for one dpi and then have everything magically adjusted when running on other dpi's.
Fortunately, Flex provides this solution in its application scaling feature.
Application scaling
Application scaling is a Flex feature designed to handle the different dpi's you'll encounter on mobile devices. It lets you hand off to Flex the problem of adapting to different dpi's. I'll explain how this works in a moment, but first we need to cover dpi categories.
When using the application scaling feature you don't deal with actual device dpi's but instead with three dpi categories. At runtime the framework determines your device's actual dpi and then maps it into one of these three categories:
device dpi runtime value | mapped to... |
---|---|
less than 200 dpi | 160 dpi category |
from 200 to 279 dpi | 240 dpi category |
280 dpi and higher | 320 dpi category |
To enable application scaling you assign a dpi category value to applicationDPI, which is a property of your app's root class (e.g. ViewNavigatorApplication). Valid string values for applicationDPI are 160, 240, 320 (or use DPIClassification contants DPI_160, DPI_240, DPI_320). Example:
<s:ViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" firstView="views.DEPhoneHomeView" splashScreenImage="@Embed(source='assets/icons/world_256x256.png')" applicationDPI="240">
The value you assign to applicationDPI is the dpi category for which your app is optimized. In other words, if you set applicationDPI="240" you're saying to Flex: "when dpi is 240 don't do anything, I've got that covered, but for devices in the 160 dpi category I need you to scale things down and for devices in the 320 category scale them up".
The benefit of application scaling is that it lets you write your app to a single dpi and then Flex deals with other dpi's, scaling things up or down as required. For example, if you've sized and laid out everything for 160 dpi you would set applicationDPI="160" and then:
- when your app is running on a device with less than 200 dpi (i.e., falls into the 160dpi category) Flex will do no scaling
- when device dpi is between 200 to 279 Flex will scale your app up by 1.5x (160 * 1.5 = 240)
- on devices 280 dpi or higher Flex scales your app up by 2x (160 * 2 = 320)
To be clear, Flex doesn't actually scale everything. That is, it doesn't just stretch pixels (though it will do that for a bitmap). For text it simply changes the font size (more efficient and result is more readable). And for Flex mobile components it's all in the skin — mobile skins include code that sets component properties appropriate for the 160/240/320 dpi categories. That's a primary task of mobile skins — look at some mobile skin code and you'll see a section that queries dpi and then adjusts to it by setting appropriate spacing, choosing the right dpi-specific assets, etc. Here is a code snip from Button's mobile skin that illustrates this:
case DPIClassification.DPI_320: { upBorderSkin = spark.skins.mobile320.assets.Button_up; downBorderSkin = spark.skins.mobile320.assets.Button_down; layoutGap = 10; layoutCornerEllipseSize = 20; : : break ; } case DPIClassification.DPI_240: { upBorderSkin = spark.skins.mobile240.assets.Button_up; downBorderSkin = spark.skins.mobile240.assets.Button_down; layoutGap = 7; layoutCornerEllipseSize = 15; : :
Keep in mind that many Spark components don't have mobile skins. Those that don't generally aren't recommended for mobile in large part because they won't adjust to dpi.
As for images, Flex will scale these up or down for you as needed, which is fine for vector graphics which scale nicely but not so good for bitmaps. To handle this Flex 4.5 has the new MultiDPIBitmapSource class, which is covered in the next section.
As noted previously, Jason San Jose has several good posts that cover mobile and multiscreen, here's two that address application scaling:- Flex mobile skins — Part 2: Handling different pixel densities
- Comparing CSS Media Queries vs. Application Scaling
Also useful on this topic is Adobe doc "Support multiple screen sizes and DPI values in a mobile application"
MultiDPIBitmapSource
MultiDPIBitmapsource is a new class you can use with components that use bitmaps — for example, the Image class, or the Button class (which accepts a bitmap for its icon). MultiDPIBitmapSource lets you specify three density-specific bitmaps which AIR then uses conditionally based on dpi — e.g., it uses your source160dpi bitmap for devices in the 160dpi category. This avoids bitmap scaling and the resultant distortion.
Use of MultiDPIBitmapsource is straightforward: just provide a trio of bitmaps optimized for each target dpi (so, one each for 160, 240, and 320 dpi). Here's an MXML usage example:
<s:Button id="selectCountryButton" label="Select country" click="selectCountryButton_clickHandler(event)"> <s:icon> <s:MultiDPIBitmapSource source160dpi="@Embed('/assets/icons/world_24x24.png')" source240dpi="@Embed('/assets/icons/world_32x32.png')" source320dpi="@Embed('/assets/icons/world_48x48.png')"/> </s:icon> </s:Button>
With the above code a 326 dpi iPhone 4 would display world_48x48.png since the iPhone 4 falls into the 320 dpi category.
JasonSJ has a good post on this, "Using Bitmaps in Flex Mobile Projects"
Dealing with your desktop's dpi as well as 160/240/320
MultiDPIBitmapsource works great for mobile, but read this from the Adobe doc DPI-Dependent Bitmaps - Functional and Design Specification : "When BitmapImage and Image are used on the desktop with MultiDPIBitmapSource, they will use the source160dpi property as the source."
So, if your desktop or browser-based versions encounter a component that uses MultiDPIBitmapsource it will display the 160dpi image, and that's probably a larger image than you want on your 100dpi screen. Of course, if you're using skins appropriately, having one for mobile and another for non-mobile, then you can just not use MultiDPIBitmapsource when specifying bitmaps in your non-mobile skin. However, sometimes using skins seems overkill, and in these cases you may be able to use adaptive design to address this. Here's an example: I had a simple popup that was mostly text but also contained an image. Obviously I wanted to use MultiDPIBitmapSource for my mobile targets to handle the range of phone dpi's but for my desktop version I wanted a 100dpi-optimized bitmap, not some 160dpi bitmap. To do this without using skins one solution is to simply use AS to query the OS and assign the image dynamically as follows:
1. Use MultiDPIBitmapSource to assign your dpi-dependant bitmaps, thus handling your multiple mobile dpi's...
<s:Image id="closeIcon" click="closePopup()"> <s:source> <s:MultiDPIBitmapSource source160dpi="@Embed('/assets/icons/close_24x24.png')" source240dpi="@Embed('/assets/icons/close_32x32.png')" source320dpi="@Embed('/assets/icons/close_48x48.png')"/> </s:source> </s:Image>
2. ...but also include the image you want to use for your desktop version:
// embed image used for desktop version [Embed(source='assets/icons/Close_16x16.png')] public var closeIconImageNonmobile:Class;
3. ...and then at runtime dynamically override the MultiDPIBitmapSource assigns based on the runtime dpi:
// for desktop version override multiDPIBitmapSource values if (Capabilities.screenDPI < 120) closeIcon.source = closeIconImageNonmobile ;
Note that this technique is fine in moderation, for simple views, but mostly you should be using skins.
"Manually" handling dpi
Note that you don't have to use the application scaling feature. To disable it just don't set an applicationDPI value. At that point you are responsible for dealing with dpi differences (note that Flex framework components with mobile skins will still adjust to dpi). To "manually" handle dpi you generally use CSS media queries. They let you set a property value like a skinclass or assisgn a style like a button's icon at runtime based on dpi. For more info on manually handling dpi see the Adobe doc "Support multiple screen sizes and DPI values in a mobile application"
Dynamic layout including screen rotation
Most Flex apps already take advantage of the framework's layout and sizing features to handle browser resizing. Here we're talking about using things like VerticalLayout, or maybe using BasicLayout with constraints, assigning percentage-based sizing, responding to events such as Event.RESIZE.
On mobile you often use these same features to handle screen differences and screen rotation between landscape and portrait.
A multiscreen app also leverages these features to handle differences between desktop and mobile versions. For example, a selection list in a multiscreen view might use VerticalLayout on a small-screen phone but switch to TileLayout for its desktop version to take advantage of that larger screen.
AIR provides mobile-specific features relating to device orientation (e.g. StageOrientationEvent), and the AIR mobile class ViewNavigator has built-in landscape and portrait states. However, if your code makes use of mobile-specific features then that code won't be useable for non-mobile versions of your multiscreen application. Code that you want to share across mobile and non-mobile needs to rely on functionality common to both. In some cases you can address this with conditional code based on an isSupported property (e.g., Stage.supportsOrientationChange). However, if you don't need all the info available from these mobile-specific features (e.g., obtain orientation relative to the devices normal orientation such as StageOrientation.ROTATED_RIGHT) but instead just need to detect rotation and current aspect ratio you can use your old friend Stage.resize. Here's some simple code for this:
Set the listener:
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:components="com.dlgsoftware.sharedviews.view.components.*" resize=" resizeHandler(event)" >
...and the handler determines orientation and responds appropriately (here setting a state):
protected function resizeHandler(event:ResizeEvent):void { var isPortrait:Boolean = height > width; currentState = (isPortrait ? "portrait" : "landscape") }
States are very useful for driving layout. Here's a snip of code that adjusts the view to the current orientation:
<s:Group top="0" bottom="10" left="0" right="0"> <s:layout.portrait> <s:VerticalLayout paddingTop="20" paddingLeft="5" paddingRight="5" gap="30" horizontalAlign="center" verticalAlign="middle"/> </s:layout.portrait> <s:layout.landscape> <s:HorizontalLayout paddingTop="0" paddingLeft="15" paddingRight="15" gap="10" horizontalAlign="center" verticalAlign="middle"/> </s:layout.landscape> <s:Group width.portrait="100%" width.landscape="50%"> : :
For a popup your resize handler will likely throw in a call to center the popup and may need to do some dynamic resizing of your popup or change the layout. Example:
protected function resizePopup(event:ResizeEvent=null):void { this.width = this.stage.stageWidth * .94 ; this.maxHeight = this.stage.stageHeight * .94 ; if (this.stage.stageWidth > this.stage.stageHeight) { // landscape group1.layout = new HorizontalLayout() ; group2.layout = new HorizontalLayout() ; group3.layout = new HorizontalLayout() ; } else { // portrait group1.layout = new VerticalLayout() ; group2.layout = new VerticalLayout() ; group3.layout = new VerticalLayout() ; } PopUpManager.centerPopUp(this); }
An interesting (though a bit dated) devnet article on this topic is Cantrell's "Authoring mobile Flash content for multiple screen sizes"
Michael Chaize has done some interesting work with dynamic layout on tablets:
- ADC video: http://tv.adobe.com/watch/adc-presents/dynamically-adapt-the-layout-of-tablet-and-mobile-apps/
- A post on his site Riagora: "Adaptive UI: mobile vs tablet"
And here's yet another Jason San Jose post, this on Adapting Flex mobile projects based on form factor
Skinning
This assumes you're familiar with "normal" Flex 4 skinning but not with mobile skinning.
Skinning is a great way to deal with many of the differences between mobile and non-mobile. Skinning seperates your component's UI from its data and logic. This lets you create multiple skins for a component — i.e., different UIs for its functionality. For multiscreen applications that include desktop targets you generally create at least two skins for your components, one with appearance/layout/sizing for your large-screen desktop version and another (often more compact) skin for mobile (which will also have code to handle different mobile dpi's, e.g., use of MultiDPIBitmapSource). Of course, multiscreen apps that target mobile-only may also need multiple skins — e.g., different skins for phone and tablet.
Skins also come in handy when you need to deal with differences in mobile UI conventions. A great example of this is ViewMenu.
Android devices have a menu button that, when pressed, displays a slide-up menu container with options buttons. Flex provides you its ViewMenu class for creating this slide-up menu of options. ViewMenu is just a container where you put your options, and Flex displays it for you whenever the menu button is pressed. All in all it acts and is styled exactly as you'd want for Android.
But what about iOS? It has no menu button, though you can provide an options button in your UI to call ViewMenu. But the standard Flex ViewMenu looks very un-iOS, not to iPhone UI conventions. However, iPhone users do have a slide-up options menu called an Action Sheet. Action Sheets have a different appearance than what you get with the default ViewMenu, but basically they provide the same functionality. As soon as you encounter this situation — different appearance but same functionality — you should think skins. In fact Jason San Jose has already created an iOS skin for ViewMenu (and other classes). Links to his work are below in the Themes section.
As for applying skins, you can do this through AS and MXML and CSS. However, using CSS to assign your skins is probably the way to go with multiscreen, letting you use CSS style sheets to centralize and simplify applying the appropriate UI to a target. For example, your desktop/browser versions might employ CSS such as:
components|PopMsg
{
skinClass:ClassReference("com.dlgsoftware.sharedviews.skins.PopMsgSkin")
}
...while your tablet version can have its own CSS that wires up a mobile skin to the same component:
components|PopMsg
{
skinClass:ClassReference("com.dlgsoftware.tablet.skins.PopMsgSkin") ;
}
It's important to note that mobile skins are significantly different from "normal" non-mobile skins. Because of the limitations of mobile devices mobile skins aren't created using MXML. Instead, they're written in AS for efficiency. Also for efficiency they generally don't use layout classes like VerticalLayout but instead use explicit positioning (so they use things like x= and y= but not bottom= or right=). And to support application scaling mobile skins include code for handling the three AIR dpi categories: 160, 240, 320. All of this means that mobile skins are more complex than those used for desktop/browser (and means you can't create mobile skins with Catalyst).
Though it's strongly recommended that you use ActionScript for mobile skins you can use MXML skins on mobile. For more info see JasonSJ's dev-week presentation (see references below). |
Mobile skinning is a huge topic, but luckily Jason San Jose has already written not just one great post on this topic but three of them. Since these posts do touch on multiscreen issues I won't spend any more time on this. Here are links to his mobile skinning content:
- Flex mobile skins — Part 1: Optimized skinning basics
- Flex mobile skins — Part 2: Handling different pixel densities
- Flex mobile skins — Part 3: Multiplatform development
- jasonsj's Creating Performant Skins and Item Renderers for Mobile Applications (pdf)
Also excellent on this topic is the chapter on Skinning in "Developing Mobile Applications with ADOBE FLEX and ADOBE FLASH BUILDER". Here is the entry point for the online version.
CSS and Themes
CSS can take you a long way toward dealing with the visual differences of mobile v. non-mobile. As noted in the previous section, you can use CSS to wire up different skins to your components. CSS is also useful for styling your application to the UI conventions of the device it's running on. This is important to making your app look like a native app — you don't want iOS icons on an Android device, and on Apple devices you want buttons with that beveled look and want to have your titles left-aligned (not centered as they are on Android), etc.
It's common to have separate CSS files for desktop, tablet and phone. You may also want separate CSS files for your different phone targets — for example, to handle differences between iOS and Android you may create a separate CSS file for each. In fact, the variation in Android devices can require CSS statements specific to different classes of Android phones.
Having multiple CSS files is appropriate to some situations, but there is another option. You can also have one CSS file that conditionally selects CSS statements based on the current OS or DPI using media queries.
Media queries
One useful new Flex 4 feature is CSS media queries, which let you conditionally apply CSS rules at runtime. Currently Flex provides two types of media rules: os-platform and application-dpi. Here's an example from the Adobe multiscreen reference application SalesDashboard:
@media(os-platform:"IOS") { s|ActionBar { defaultButtonAppearance:beveled; } .backButton { skinClass:ClassReference("spark.skins.mobile.BeveledBackButtonSkin"); } }
Since media queries are resolved at runtime, not compile time, anything a media query might select for (skins, embedded bitmaps, etc.) is compiled into your app. This ensures availability at runtime. So, if you have an os-platform media query that selects some skins for Android and others for iOS then both sets of skins become part of your app's package. This increases the size of your app with a set of skins that you won't actually use. One option for avoiding this is to replace that runtime os-platform query with target-specific CSS and then use explicit compiles for each target. In other words, you could compile once for Android and again for iOS, each time providing only the skins/assets you need for that particular target. The result of this would be an .ipa with just your iOS-related skins/assets but not those needed for Android, and vice versa. A good way to do this is with themes.
Themes
Themes are swc's that can contain visual parts of your app — CSS, skins, embedded bitmaps, fonts, FXG, etc. You compile these into your theme swc and then when compiling your target versions you'll pass the name of your theme swc as an argument to the compiler's -theme option. This allows you to package a target-specific theme into your target output (e.g., an Android-specific theme into an .apk). This also makes it easy to change the look of your application by simply providing different themes at compile time. Note that the themes you pass are generally additive — that is, you append to existing themes like the mobile theme. Example:
-theme+=..themes/iosTablet.swc
Jason San Jose has created a theme that goes a long way toward styling your app to iOS conventions. See his post iOS Theme for Flex Mobile Projects — Proof of Concept and video. |
To sum up, CSS can be used to style your app to UI conventions and to wire up skins. You can do this dynamically at runtime based on OS or dpi through media queries. You generally use multiple stylesheets, at a minimum using separate stylesheets for your mobile and non-mobile targets. For efficiency you may want to use themes to store and apply different "faces" to the different versions of your multiscreen application.
BTW, you can't handle all differences through CSS. One example of this is a Back button for iOS. While Android has a built-in Back button there's none for iOS. A common solution is to put a Back button in the navigationContent area of your mobile views and then hide/unhide that button based on the current OS (i.e., the button is always there, but it's hidden when running on Android).
Here are two more Jason San Jose references that address styling and UI conventions:
- Using CSS for phone and tablet UI
- His Skinning part 3 post includes content on CSS and styling to UI conventions
Here is a useful livedocs intro to themes.
Dylan dePass has a nice bit on all this about halfway through his Max 2011 presentation (video) "Putting the Multi in Multiscreen"
Architecture
This section mostly deals with non-screen issues. Note that some content is more relevant to applications that target both mobile and non-mobile as opposed to mobile-only.
The Architecture section is still under construction. I hope to push an update in February.
In a multiscreen application your code often has to serve three masters: phones, tablets and PC's/laptops. The different capabilities of these targets can complicate code reuse. This is especially true when your targets include both mobile and non-mobile. Code that runs fine on the desktop can crawl on mobile; code that processes many large images can run without problems on a large-memory desktop machine but cause crashes on mobile. This section addresses these types of issues.
Available components
While this Architecture section is focused primarily on non-screen issues something mentioned previously bears repeating here: the Flex framework components available to AIR mobile apps are a small subset of what's available to an AIR desktop or Flex browser-based application. On mobile you can't use Halo (mx) components (exception: charting components) and you are limited to a relatively small subset of Spark components. For example, not only is the Halo ComboBox out of bounds, you also can't use the Spark ComboBox because it doesn't have a mobile skin and its "normal" skin doesn't work on mobile.
Though more Spark components will get mobile skins over time the current list of mobile-ready components is slim pickings. In some cases this isn't such an issue. ComboBoxes aren't common on phones, it's better to pop a full-screen list. Still, whenever possible you'd like all target versions of your application use the same components.
Note that some Spark classes that can be used for mobile really shouldn't be. One example is RichEditableText: although you can use it on mobile Adobe recommends you don't for performance reasons. Another example is ItemRenderers: although you can use the ItemRenderer class on mobile it can yield poor scrolling performance in mobile lists (which is why Adobe provides mobile-optimized LabelItemRenderer and IconItemRenderer).
A list of components recommended for mobile use in 4.5 is here and and for 4.6 is here.
Performance, memory, bandwidth
While a desktop application should always be optimized for performance you must pay special attention to code you'll also execute on mobile. For shared code you're constrained by that weakest link — mobile performance is still vastly inferior to that of desktop machines. There's even a division within mobile: tablets generally have more horsepower than phones. Finding performance problems is usually straightforward, but it's worth mentioning that you should do testing early on actual devices — don't assume that the emulator mimics mobile performance as well as appearance, it doesn't.
As with performance, memory of mobile devices is inferior to their desktop counterparts. This makes mobile generally less forgiving when it comes to memory use. An example of this is memory leaks. Of course desktop applications should always clear event listeners when done with them (or allocate with useWeakReference). Failing to do this will inhibit garbage collection and create memory leaks. However, this is even more important on mobile where you have much less memory available.
Peak memory is also an issue. Code that processes many large images might run without problems in the desktop environment but on mobile cause crashes when the OS kills your app for using an unacceptably high percentage of available memory. Even code that's meticulous about memory can run into problems on mobile — while garbage collection on mobile is more aggressive you can outpace it (on this see my post "AIR for Android memory issue with large images").As for bandwidth and battery, a design that's acceptable for a desktop application might need a redesign for mobile where connections are sometimes slow and where each remote access draws on the device's finite power source. Duane Nickull has a tutorial (post and video) that illustrates this nicely. In this tutorial he creates an RSS reader as you might create it in a desktop environment, but he stops midway through the tutorial to points out "an architectural mistake" that results in too-frequent trips to the server. He then shows how caching data locally can reduce use of both bandwidth and battery. You might not care about this in a desktop app, but for code you'll run on mobile you should always consider ways to reduce both bandwidth and battery use.
Another design consideration is network availability. Unlike desktop and browser applications your mobile apps can't assume a consistent network connection. Once again, caching data locally is something to consider in your design. This also impacts bitmaps use. A browser-based app might avoid embedding bitmaps to keep swf size down but a mobile app can't count on a remote bitmap being available. To address this you can use target-specific CSS, embedding images for your mobile targets but pointing to server-based images for your browser-based version.
Here's a good devnet post on mobile performance optimization: "Flex mobile performance checklist" and here is an Adobe TV Max 2011 presentation on the topic.Passing data
As with many items in this section, this applies to multiscreen code that will execute in both mobile and non-mobile environments rather than one with all-mobile targets.
Multiscreen can affect how your application communicates with itself. Consider the example of master/detail views. In a desktop application you might implement this with two panes, each pane containing a view, a master view on one side of the screen with a list of employees, a detail view on the other side showing details for the currently selected employee. When an employee is selected in the master's list it fires a EMPLOYEE_CHANGE event. The detail view has set a listener for this event and its event handler updates the detail display with info on the selected employee. Straightforward. But on a phone (and possibly a tablet) that small screen means you won't have both of these views displayed at the same time. This means they aren't both instantiated (in memory) at the same time. In this case having that master view fire an event with data for the detail view is a bit like shouting in an empty room -- not likely to elicit any response since no one is listening.
Phone apps generally handle master/detail by replacing that master view with the detail view when an employee is selected. In an AIR mobile app you'd execute a navigator.pushView
to replace the master view with the detail view, and the employee data is usually passed along via the pushView's data
parameter. Of course you can't share this same code with desktop and browser applications because ViewNavigator is a mobile-only class.
The above presents a challenge to code reuse. You'd prefer your master and detail views communicate the same way on mobile and non-mobile. How can you architect your application for this? One way is to employ a persistent data model for storing shared application data. You can then have your views work through that model, binding to values stored there. Because binding uses events your the detail view will respond to data changes when used in non-mobile but when it's displayed on mobile it will have access to (and be able to populate its fields from) that shared data.
Displaying text including HTML
Flex and AIR have numerous components for displaying text including HTML markup. However, the text-handling options available to mobile apps differs significantly from those available to a desktop and browser-based applications. This is true even for individual components: some handle text (especially HTMLText) considerably differently when running on mobile. Since I've already covered this topic in my "AIR Mobile Tips" post I won't repeat it here — here is a link that will take you directly to the text handling section of that mobile tips post.
Mobile event viewActivate
Desktop and browser applications often execute view and component initialization code in event handlers for events like creationComplete, initialize, etc. However, on mobile you generally execute init code on the View class's viewActivate event, which fires after any transition has completed. Without this your setup processing can conflict with the transition, causing a jerky transition (of course, you could suppress those transitions, but mobile users have come to expect these). Because the viewActivate event is mobile-specific you cannot use it for non-mobile. Obviously this can be an issue for views you want to use on both mobile and non-mobile targets.
One way to address this is to wrap a non-mobile view in the mobile View
class when you want to use it on mobile, instantiating your class only after viewActivate has fired. I admit this feels too much like a kludge, especially since the transition into your view will not show the view but a blank screen with "Loading" text. However, sometimes it can be useful, most especially if you have a heavy view that takes time to instantiate (because the view must be instantiated before the transition can start, and in this case you can have a long lag before anything happens). Here's a simplified version of what I'm talking about:
<?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" xmlns:dataviewer="com.dlgsoftware.sharedviews.dataviewer.*" title="Drilldown Graph" viewActivate="onViewActivate()" > <fx:Script> <![CDATA[ private function onViewActivate(e:Event):void { currentState = "doneLoading"; } ]]> </fx:Script> <s:states> <s:State name="loading"/> <s:State name="doneLoading"/> </s:states> <s:Label text="Loading..." includeIn="loading" verticalCenter="0" horizontalCenter="0" /> <dataviewer:DrilldownGraph includeIn="doneLoading"/> </s:View>
Project organization in Flash Builder
This is a placeholder -- I'll cover this topic in a separate post that I'll push in February. Here's the intro to that post:
With a multiscreen app you want to reuse code as much as possible, but how does this translate into project creation/linkages within Flash Builder? Some code will be shared — for example, you should be able to reuse nearly all of your data access layer (the Model of MVC). On the other hand, your UI's views will have less reuse since your target screens can differ widely — a view that works well on a PC display isn't likely to work on a phone's much smaller screen with its much greater pixel density. And you'll need some project-specific code so you can leverage features specific to a target environment (e.g., for non-mobile versions you might want to use mx components like TabNavigator and comboBox, while on mobile you'll need to set AIR project properties like permissions, your app's icon, etc.).