Tag Archives: development

Flex 4.6 is Available AND I’m on TV!

(Adobe TV, that is)

In case you had not seen on the Flex team blog, twitter or through some other medium, Flex SDK 4.6 and Flash Builder 4.6 were released today!  Go get them, if you have not done so already.  Flex 4.6 marks a huge advancement for the Flex SDK, especially regarding mobile applications.

Flash Builder 4.6 is a FREE update for Flash Builder 4.5 users. From the Flex team blog:

A lot is included in this update, so much so that we couldn’t deliver it in the Adobe Application Manager. This means Flash Builder 4.5 users won’t automatically be notified about the update and will have to download the full Flash Builder 4.6 installer and enter their Flash Builder 4.5 serial number.

You can download the open source Flex SDK at: http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+4.6.

Or, you can download Flash Builder 4.6 from: https://www.adobe.com/cfusion/tdrc/index.cfm?product=flash_builder.  Flash Builder 4.6 release notes are available at http://kb2.adobe.com/cps/921/cpsid_92180.html

Note: you must uninstall Flash Builder 4.5.1 to install Flash Builder 4.6.  

You can read specifics about what’s new in Flash Builder 4.6 on the Adobe Developer Connection at: http://www.adobe.com/devnet/flash-builder/articles/whatsnew-flashbuilder-46.html, and what’s new in Flex SDK 4.6 at: http://www.adobe.com/devnet/flex/articles/introducing-flex46sdk.html

Coinciding with the Flex & Flash Builder releases, new content around Flex and Flash Builder 4.6 have been posted on Adobe TV.   There is a bunch of great new content worth checking out, including fellow evangelist Michael Chaize’s adaptive UI for different platforms and device form factors.   In addition, here I am speaking out the new Captive Runtime feature introduced in AIR 3…

Captive Runtime for Mobile

Captive Runtime for Desktop

Building PhoneGap Applications With Dreamweaver

Update (3/28/2012): 

Hi Everyone, I know there have been lots of questions about PhoneGap + Android SDK, and lots of headaches because Google’s Android SDK keeps changing and breaking the Dreamweaver integration. Fret not! There is an easier way — Adobe has released a plugin to integrate Dreamweaver with PhoneGap Build. With this plugin, you build your experience in Dreamweaver, then push to the PhoneGap Build service for cloud-based compilation of device-specific binaries. Read more about this plugin here: http://blogs.adobe.com/dreamweaver/2012/04/phonegap-build-extension-for-dreamweaver-cs5-5.html

Original Post:

PhoneGap apps are built with HTML and JavaScript, and can be created with any IDE or text editor. You can build them in xCode or Eclipse. Did you also know that you can build PhoneGap apps within Dreamweaver, and you can even launch and debug on the iOS Simulator and Android Emulator all from within Dreamweaver?

Here’s a video of this in action from Adobe TV. After the video, we’ll walk through this process step by step.

In order to use the iOS simulator and Android emulator, you’ll need to download and install xCode and the Android SDK.   Once you’ve downloaded those, let’s focus on setting up your project within Dreamweaver.

The first thing that you need to do is create a new “site” within Dreamweaver for your PhoneGap application.  Go to the “Site” menu, and select “New Site…”.

The site setup/details dialog will be displayed.   Go ahead and give it a name and directory to contain project files and resources.

Next, we need to create the main application file.  Within Dreamweaver, go to “File”, and select “New…”.  Then select the “Page from Sample” option, sample folder “Mobile Starters”, then select the page template for “jQuery Mobile (PhoneGap)” and click “Create”.

This will create a new HTML file for the mobile project.  Go ahead and save the file you just created.   The first time this file is saved, you will be prompted to copy dependent files.  Click “Copy” to copy the dependent files into your application “site”.

Once in Dreamweaver, you can edit the HTML and JavaScript to your heart’s content.   You can take full advantage of Dreamweaver’s code view or design view, live previews, and any other features.

Once you are ready to build and deploy to the android and iOS simulators, you’ll need to setup the mobile development configuration.   First, we’ll need to configure the application frameworks.   Within Dreamweaver, go to the “Site” menu, select “Mobile Applications”, then select “Configure Application Framework”.

The “Configure Application Framework” dialog will be displayed.   Here, you’ll need to enter the full path to your Android SDK, and the path to the iOS Developer tools (xCode).

Once you have the application frameworks configured, you’ll need to configure your application’s settings.    Go to the “Site” menu, select “Mobile Applications”, then select “Application Settings…”.

Within the “Native Application Settings” dialog, you’ll need to specify your application bundle id (the unique id for the application), an application name, the author, version, and application icons.   You can also select iOS SDK versions, and which Android emulator to use.

Once you’ve configured the application settings and application frameworks, you are ready to build your app and run in the emulators.   Just go to the “Site” menu, select “Mobile Applications”, select “Build and Emulate”, then choose a device or platform.

Once you chose a platform or device, Dreamweaver will go ahead and launch the appropriate emulator or simulator, and launch the application.

If you run into issues with the Android SDK, first make sure that you are using the latest Dreamweaver SDK following the instructions at http://blog.assortedgarbage.com/2011/05/resolving-android-sdk-failed-to-install-with-dreamweaver-cs5-5/

If you still have issues with deploying to Android and see the error message below in the build log, then you are probably using the latest Android SDK that was recently released in October.

Install file not specified.

 'ant install' now requires the build target to be specified as well.

     ant debug install
     ant release install
     ant instrument install
 This will build the given package and install it.

 Alternatively, you can use
     ant installd
     ant installr
     ant installi
     ant installt
 to only install an existing package (this will not rebuild the package.)

The latest Android SDK introduced an additional parameter that is not yet supported by the PhoneGap integration kit within Dreamweaver. You can fix this by updating the build.xml file for the application instance to override the “install” target and add the required dependencies which make this error go away.

Go to you application build director and open the “build.xml” file. This will be inside a folder named after the bundle_ID within your target directory. You can find the target directory within the “Application Settings” dialog, as shown below:

In my case, the build directory is /Users.triceam/Destkop/com.company.phonegapsample_Android

Find the “import” node below (at the end of the file):

<import file="${sdk.dir}/tools/ant/build.xml" />

Add to this line the attribute ‘as=”imported”‘ and a new “install” target that will override the existing “install” target as shown below. This build target will utilize the existing “install” target, and add necessary debug file dependencies to fix the build error shown above.

<import file="${sdk.dir}/tools/ant/build.xml" as="imported" />
<!-- Override the target to add the dependency -->
<target name="install"
   depends="-set-debug-files,imported.install"  />

Not only is Dreamweaver CS5.5 a best-of-breed solution for building web content, with PhoneGap support, Dreamweaver is now a best-of-breed solution for building cross-platform mobile applications as well. Using the PhoneGap integration within Dreamweaver allows you to use familiar tools, familiar development processes, and your current web development skills to build exciting new mobile applications. This enables you, as the developer or designer to focus on what matters – the application or content within your mobile scenarios.

“Almost Native” – iOS Apps Powered By iAd.js and PhoneGap

One of the benefits of using a cross-platform development technology that I mentioned in my last post is that cross-platform development technologies can be solution accelerator platforms.  I mean “solution accelerator” because it can be easier and faster to develop an application using cross-platform technologies (web development skills) rather than native development, even if you are only targeting a single platform.  This isn’t the case for every solution, but certainly can be for many scenarios.

Here’s a sample application that I put together while exploring the PhoneGap product offering.   For those that weren’t aware, Adobe has entered an agreement to acquire Nitobi, the makers of PhoneGap, and PhoneGap is an HTML5 app platform that allows you to author native applications with web technologies and get access to APIs and app stores.

This application consumes data from the rottentomatoes.com API, and is built entirely in JavaScript.  If you haven’t seen it before, RottenTomatoes.com is a site for searching movie ratings & information.  Check out the video below, and then we’ll examine how the application was built (be sure to check out the CoverFlow runtime performance at 34 seconds).

The entire codebase that I had to write for this application is 289 lines of HTML & JavaScript (including whitespace), all in a single file, and was built using iAd.js, xui.js, and of course, PhoneGap.   The first thing you might be wondering is “what is iAd.js?” or, if you know about the iAd platform, you might be thinking “um… isn’t that just for advertisements?”. Everyone who uses an iOS device has probably encountered an iAd at least once, whether they know it or not.  The iAd program is an advertising platform for iOS devices that enables advertisers to create rich, engaging, & interactive advertisements.    Interestingly, all iAds are built on top of a JavaScript framework built by Apple.

It just so happens that the iAd.js JavaScript framework can also be used outside of the advertising context (only on iOS devices – I tried Android as well, but not all of the elements worked correctly).   Not all features of the iAd framework will work outside of the advertising context, such as purchasing or interacting with the iTunes store.  However the iAd.js framework will provide you with user interface elements that look nearly identical to native iOS components.  This includes view navigators, table views (with groups, disclosure indicators, etc…), carousel views, cover flow views, wheel pickers, progress bars, sliders, switches, buttons, and much more.   In addition, the interactions and animations for these components are highly optimized for mobile safari, and interaction with these elements feels very, very close to native.  There are a few minor things here and there, but overall it is not necessarily easy to distinguish the difference.

Note: I have not submitted any apps using this technique to Apple’s app store.  However, I have heard from others that Apple has accepted their applications which are built using this approach.   

While these components are instantiated in the browser and created via HTML & JavaScript, the programming model of iAd components is very similar to native iOS development.  You still have the usage of protocols (interfaces), and controller & delegate patterns.  For example, using the iAd.TableView ui component, still requires use of the TableViewDataSource and TableViewDelegate protocols (just implemented in JavaScript). Familiarity with native iOS development will definitely be a big plus if you are using the iAd.js framework.

I used xui.js to simplify the syntax for XMLHttpRequest for asynch data loading, and of course, PhoneGap is used for the application container, as well as any native OS interaction if you wanted any.  The source code could certainly be broken up into multiple controllers or separate files for maintainability, however I was just going for a “quick & dirty” example.

Basically, you just need to include the iAd JavaScript and CSS files, then build your application as you would any other HTML/JS PhoneGap experience.   You can download the iAd framework from here if you are a registered Apple iOS developer.   In theory, this isn’t that much different from using jQuery mobile components, however these have better performance on iOS, and have a more-native feel.

Thanks to pixelranger and merhl from Universal Mind for showing me a while back that you could use this approach!

Full source code below:


        <meta http-equiv="content-type" content="text/html; charset=utf-8">
        <meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
        <meta name="apple-mobile-web-app-capable" content="yes">

        <!-- Import iAd Assets -->
        <link rel="stylesheet" href="iAd/iAd.css">
        <script type="text/javascript" src="iAd/iAd.js" charset="utf-8"></script>

        <!-- Import other libraries -->
        <script type="text/javascript" charset="utf-8" src="libs/xui-2.2.0.js"></script>
        <script type="text/javascript" charset="utf-8" src="libs/phonegap-1.1.0.js"></script>

        <style type="text/css" media="screen">

            body {
                margin: 0;
                overflow: hidden;

            .ad-flow-view {
                position: relative;
                left: 0px;
                width: 300px;
                height: 380px;
                -webkit-perspective: 400;

            .ad-flow-view .ad-flow-view-camera {
                position: absolute;
                left: 50%;
                width: 10px;
                height: 10px;

            .ad-flow-view .ad-flow-view-cell {
                position: absolute;
                top: 30px;
                left: -125px;
                width: 250px;
                height: 350px;

            .ad-flow-view .ad-flow-view-cell img {
                pointer-events: none;
                width: 180px;
                height: 267px;
                -webkit-box-reflect: below 0px -webkit-gradient(linear, left top, left bottom, from(transparent), color-stop(0.8, transparent), to(rgba(255,255,255,0.35)));


        <script type="text/javascript" charset="utf-8">

            var API_KEY = "put your api key here";

            var sampleData;

            /* ==================== Controller ==================== */

            var controller = {
                data : []

            controller.init = function () {
                var url = "http://api.rottentomatoes.com/api/public/v1.0/lists/movies/box_office.json?limit=15&country=us&apikey=" + API_KEY;
                console.log( url );

                x$().xhr( url, {
                         async: true,
                         callback: function() {

                            var trimmedResponse = this.responseText.replace(/^\s\s*/, '').replace(/\s\s*$/, '');

                            if ( trimmedResponse.length > 0 )
                                sampleData = eval( "(" + trimmedResponse + ")" );

                                if ( sampleData )
                                    controller.data =  sampleData.movies ;


                this.navigation = new iAd.NavigationController();

                this.navigation.delegate = this;

                this.navigation.navigationBar.barStyle = iAd.NavigationBar.STYLE_BLACK;
                this.navigation.pushViewControllerAnimated(this.createTableViewController(), false);

            controller.handleEvent = function (event) {
                if ( event.type == iAd.View.TOUCH_UP_INSIDE_EVENT ) {
                    var viewController = this.createFlowViewController();
                    this.navigation.pushViewControllerAnimated(viewController, true);
                    viewController.view.hidden = false;

            controller.displayDetails = function (index) {
                var item = this.data[index];
                var viewController = this.createDetailViewController(item);
                this.navigation.pushViewControllerAnimated(viewController, true);
                viewController.view.hidden = false;

            controller.createTableViewController = function (index) {

                if ( this.viewController == null ) {
                    this.viewController = new iAd.ViewController();
                    this.viewController.title = "Rotten Tomatoes";

                    this.viewController.navigationItem.rightBarButtonItem = new iAd.BarButtonItem();
                    this.viewController.navigationItem.rightBarButtonItem.style = iAd.BarButtonItem.STYLE_DONE;
                    this.viewController.navigationItem.rightBarButtonItem.title = 'Flow';
                    this.viewController.navigationItem.rightBarButtonItem.addEventListener( iAd.View.TOUCH_UP_INSIDE_EVENT, this, false );

                    // create a TableView
                    this.table = new iAd.TableView();
                    this.table.tableStyle = iAd.TableView.STYLE_GROUPED;
                    this.table.delegate = this;
                    this.table.dataSource = this;
                    this.table.size = new iAd.Size(window.innerWidth, window.innerHeight-46);


                return this.viewController;

            controller.createFlowViewController = function (index) {
                var viewController = new iAd.ViewController();
                viewController.title = "Flow";

                var flowView = new iAd.FlowView();
                flowView.dataSource = this;
                flowView.delegate = this;
                flowView.layer.style.backgroundColor = "#FFFFFF";

                // customize flow view
                flowView.sidePadding = 150;
                flowView.cellRotation = 65;
                flowView.cellGap = 50;
                flowView.dragMultiplier = 1.0;
                flowView.sideZOffset = -200;

                // load the data
                this.flowView = flowView;


                viewController.view.hidden = true;
                this.flowSelectedIndex = 0;
                return viewController;

            controller.flowViewNumberOfCells = function(flowView) {
                return this.data.length;

            controller.flowViewCellAtIndex = function(flowView, index) {
                var cell = document.createElement('div');
                cell.appendChild(document.createElement('img')).src = this.data[index].posters.detailed;
                return cell;

            /* ==================== iAd.FlowViewDelegate Protocol ==================== */

            controller.flowViewDidTapFrontCell = function (flowView, index) {
                console.log('flowViewDidTapFrontCell ' + index + ", " + this.flowSelectedIndex);
                if ( this.flowSelectedIndex == index )
                    this.displayDetails( index );

            controller.flowViewDidSelectCell = function (flowView, index) {
                console.log('flowViewDidSelectCell ' + index);
                this.flowSelectedIndex = index;

            controller.flowViewDidBeginSwipe = function (flowView) {

            controller.flowViewDidEndSwipe = function (flowView) {

            controller.createDetailViewController = function (item) {
                var viewController = new iAd.ViewController();
                viewController.title = item.title;

                var scrollView = new iAd.ScrollView();
                scrollView.userInteractionEnabled = true;
                scrollView.size = new iAd.Size(window.innerWidth, window.innerHeight-46);
                scrollView.horizontalScrollEnabled = false;
                scrollView.layer.style.backgroundColor = "#FFFFFF";

                var imageView = scrollView.addSubview(new iAd.ImageView());
                imageView.image = iAd.Image.imageForURL( item.posters.profile );
                imageView.position = new iAd.Point(10, 10);
                imageView.size = new iAd.Size(120, 178);

                var synopsisLabel = scrollView.addSubview(new iAd.Label());

                synopsisLabel.text = item.synopsis;

                synopsisLabel.numberOfLines = 0;

                synopsisLabel.size = new iAd.Size(this.navigation.view.size.width-150, 800	);
                synopsisLabel.position = new iAd.Point(140, 10);
                synopsisLabel.autoresizingMask = iAd.View.AUTORESIZING_FLEXIBLE_WIDTH | iAd.View.AUTORESIZING_FLEXIBLE_HEIGHT;
                synopsisLabel.verticalAlignment = iAd.Label.VERTICAL_ALIGNMENT_TOP;

                viewController.view.addSubview( scrollView );
                viewController.view.hidden = true;
                return viewController;

            /* ==================== TableViewDataSource Protocol ==================== */

            controller.numberOfSectionsInTableView = function (tableView) {
                return 1;

            controller.tableViewNumberOfRowsInSection = function (tableView, section) {
                return this.data.length;

            controller.tableViewCellForRowAtPath = function (tableView, path) {
                var cell = new iAd.TableViewCell();
                cell.text = this.data[path.row].title;
                cell.detailedText = 'title';
                cell.accessoryType = iAd.TableViewCell.ACCESSORY_DISCLOSURE_INDICATOR;
                cell.selectionStyle = iAd.TableViewCell.SELECTION_STYLE_BLUE;
                return cell;

            controller.tableViewTitleForHeaderInSection = function (tableView, section) {
                return "Box Office Movies";

            controller.tableViewTitleForFooterInSection = function (tableView, section) {
                return "Powered by RottenTomatoes.com";

            /* ==================== TableViewDelegate Protocol ==================== */

            controller.tableViewDidSelectRowAtPath = function (theTableView, path) {

            controller.tableViewDidSelectAccessoryForRowAtPath = function (theTableView, path) {

            /* ==================== iAd.NavigationViewDelegate Protocol ==================== */

            controller.navigationControllerWillShowViewControllerAnimated = function (theNavigationController, viewController, animated) {

            controller.navigationControllerDidShowViewControllerAnimated = function (theNavigationController, viewController, animated) {

            /* ==================== Init ==================== */

            function init () {
                console.log( "init" );

            window.addEventListener('load', init, false);