Personal tools

Views

AIR:Articles:Building Your First Flex-based Adobe AIR Application

From Adobe Labs

By Andrew Muller (http://www.webqem.com)

The application that you will be building is a simple RSS reader, which will call the Adobe XML news aggregator (popularly known as MXNA) to fetch an RSS feed of the latest articles and use the HTML control in Adobe Integrated Runtime (AIR) to load and display the page for a selected article.

Table of contents

Requirements

In order to make the most of this article, you need the following software and files:

Sample files: andrew_muller_sample_code.zip (ZIP, 316 KB)

Prerequisites: Intermediate knowledge of ActionScript and MXML.

Application setup

Once you've installed the Flex Builder 3 beta choose File > New > AIR Project to open the dialogue to create your Adobe AIR project. If you are using the Flex Builder plug in for Eclipse you will choose File > New > Other, expanding the Flex option to select AIR Project from there.

The first step of the New Adobe AIR Project dialogue should already be relatively familiar to you if you have already been developing with Flex. You will not be using an application server to deploy this project - assign a project name of MyAirRssReader leaving the server type option set to "Other/None" and choose "Next>", select an appropriate location for your project to be saved if you are not using the default and leave the Flex SDK version set to default, click on the Next > button to move to the next step.

On the next part of the dialogue you will need to assign a name to your Main application file – the process has defaulted this to the name of the project for you. Accept that name and choose Next >, you will be taken to the Application XML Properties panel. The details that you enter into this panel will be used to generate an XML based application descriptor file used by the compiler when creating the AIR file that you will eventually deploy. Enter the following details for your application and click Finish.

This will create a project in Flex Builder with a standard Flex directory structure that includes a bin directory. Two files will be created in the root of the project directory: the application file MyAirRssReader.mxml and the application descriptor file MyAirRssReader -app.xml.

Building the application

The next thing for you to do is to build the application. This will be built in stages: the first is to build and test the logic that will fetch and display the RSS feed; the second is to call and display the HTML page which is the details of a particular article; the last is to style and package the Adobe AIR application.

Add an instance of the HTTPService component to your application, set the id property to RssService and the url property equal to the URL of MXNA's RSS feed. Above that create a private function rssHandler within a Script block to process the RSS data that's retrieved by the HTTPService component. You will need to import mx.rpc.events.ResultEvent to properly type the event parameter of the function.

Although the HTTPService component has a resultFormat property that can be set to a variety of data types it's best to use the minimum of settings on the component as the component will cast it's result as an ArrayCollection which will be sufficient to use as a dataProvider for a List based component later on. Create a private variable to store the result of the HTTPService result, you will need to import the ArrayCollection class so that you can data type the variable. The last thing is to add a Button below the HTTPService component and set it's click event to call the HTTPService's send method to fetch the RSS feed.

<mx:Script>
<![CDATA[
      
import mx.collections.ArrayCollection;
import mx.rpc.events.ResultEvent;
         
[Bindable] private var rssFeedData:ArrayCollection;
private function rssHandler(event:ResultEvent):void
{
  rssFeedData = event.result.RDF.item;
}
]]>
</mx:Script>
<mx:HTTPService id="RssService" url="http://weblogs.macromedia.com/mxna/xml/rss.cfm?query=byMostRecent&languages=1" 
result="rssHandler(event)"/>
<mx:Button label="Fetch RSS" click="RssService.send()"/>

Flex Builder will validate your code as it saves it, displaying an error message in the Problems view (Window > Problems) if necessary or precompiling the application if the code is valid. Upon saving the code you've just entered, you will discover that there is a problem with the URL that you have entered for the RSS feed: the "&languages=1" argument at the end of the URL does not agree with Flex Builder. Remove that from the URL and save again. The error will be removed from the Problems view.

Insert a breakpoint beside the closing curly brace of the rssHandler function and test the application in debug mode (Run > Debug). Click on the Button once the application is rendered, this should only take a few moments as you're fetching the raw data for the RSS feed.

Once the data is retrieved you will be switched to the debug perspective of Flex Builder, expand the variables view so that you can see the structure of the data retrieved. By expanding you should be able to discover that the data resides in event > result > RDF > item which is data typed as mx.collections.ArrayCollection. Expand one of the elements of this ArrayCollection and you will be able to determine the necessary properties of the data that you will display.

Add a TileList component to the application, this will allow you to display a scrolling single column of data – dynamically populated by the ArrayCollection via the dataProvider property.

The TileList is the first visual component that you've added that requires some consideration of layout to be applied to your application, you will need to set both the width and height of the app to accommodate other elements. You will quickly discover that these are not set in the WindowedApplication tag, the result will often mean that the application will seem to disappear during testing, the dimensions are instead set in the application descriptor file MyAirRssReader-app.xml.

This was made for you when you first created the project. Open the XML file and look for the rootContent tag; you should find it around line 75 in a typical project. The rootContent tag is where you also set the systemChrome and transparent properties for the application. It's also used to set the dimensions of your application, add a width of 880 and a height of 600 to the tag.

<rootContent systemChrome="standard" transparent="false" visible="true" width="880" height="600">[SWF reference is generated]</rootContent>

A custom item renderer

The TileList component uses its itemRenderer property to nominate a custom component to control the display of each row of data to be displayed in the component. In turn, the component is populated by the dataProvider property using the ArrayCollection that we've built from the data returned by the HTTPService component's result.

The component used for the renderer is based on a Canvas component; absolute positioning has been used for the layout of the various elements in the design. Text controls have been used to allow for word wrapping. The y positions of all the components other than the title have been dynamically calculated to accommodate the possibility of the title wrapping.

The htmlText and text properties of the various components used in the renderer are bound to the properties of the data object passed to the renderer with each iteration of the TileList parent. Note that both the title and description Text controls are using their htmlText properties as a number of posts retrieved use code to represent special characters.

ItemRenderer.mxml:

 <?xml version="1.0" encoding="utf-8"?>
 <mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" horizontalScrollPolicy="off" verticalScrollPolicy="off"> 
   
   <mx:DateFormatter id="dateFormatter" formatString="D-MMM-YY"/>
   
   <mx:Text id="Title" 
      htmlText="{data.title}" 
      x="5" y="0" 
      fontSize="11" selectable="false"
      width="780"
      textAlign="left" fontWeight="bold"/>
   
   <mx:Text id="PublishDate"
      text="{dateFormatter.format(data.date)}"
      x="5" y="{Title.height+3}" 
      fontSize="9" selectable="false"
      width="200"
      textAlign="left" 
      fontWeight="bold"/>         
   
   <mx:Text id="Author"
      text="{data.creator}"
      x="68" y="{Title.height+3}" 
      fontSize="9" selectable="false"
      width="200"
      textAlign="left" fontWeight="bold"/>
      
   <mx:LinkButton id="ReadArticle"
      label="> read article"
      x="700" y="{Title.height-3}"
      fontSize="10"
      rollOverColor="#B8AF9C"
      selectionColor="#595341"
      textRollOverColor="#FFFFFF"
      textSelectedColor="#FFFFFF"/>   
      
   <mx:Text id="Description"
      htmlText="{data.description}"
      x="5" y="{Title.height+20}" 
      fontSize="10" selectable="false"
      width="780"
      textAlign="left"/>
      
   <mx:HRule x="0" y="115" width="100%"/>   
      
 </mx:Canvas>

The next thing is to build the logic to call and display the HTML page for an article selected within the TileList. A TabNavigator is added to the application so that you can switch between the RSS feed listing and a simple browser built with Adobe AIR's HTML control.

The TileList is then nested inside a VBox container to create a tab in the navigation container. A ProgressBar component is added to the VBox to provide some feedback to the user while the RSS feed is being fetched. Its indeterminate property is set to true to give it a barber's pole effect during display. Box containers have been added to the VBox to help with the layout.

The ProgressBar is nested in a Box whose height has been set to 0 so that it's hidden by default. The Button's click event is modified to change the height of that Box to reveal the ProgressBar while the HTTPService is fetching the RSS feed. The rssHandler method resets the height of the Box containing the ProgressBar to 0. Changing the height of objects to minimize or maximize their size could also be achieved in Flex with the use of States.

From within MyAirRssReader.mxml:

 <mx:TabNavigator 
   id="ContentNavigator"
   width="860" height="540" 
   x="10" y="10" paddingTop="5" paddingRight="5" paddingBottom="5" paddingLeft="5">
   
   <mx:VBox label="Articles" 
      width="100%" height="100%"
      verticalGap="0" paddingTop="10" paddingRight="10" paddingBottom="10" paddingLeft="10">
      <mx:Box width="100%" height="33" 
         backgroundColor="#B8AF9C" cornerRadius="15" paddingBottom="5" paddingLeft="10" paddingRight="5" paddingTop="5">
         <mx:Button label="Fetch RSS" click="RssService.send();RssLoader.height=30"/>
      </mx:Box>
      <mx:Box id="RssLoader" 
         width="100%" height="0" 
         backgroundColor="#B8AF9C" paddingTop="6" paddingRight="10" paddingBottom="5" paddingLeft="10">
         <mx:ProgressBar id="rssProgress" 
            indeterminate="true" 
            barColor="#B8AF9C"
            width="100%" 
            labelPlacement="center" 
            label="loading feed"/>
      </mx:Box>               
      <mx:TileList id="FeedDisplay"
         width="100%" height="100%"
         maxColumns="1"
         rowHeight="120" columnWidth="810"
         itemRenderer="ItemRenderer"
         dataProvider="{rssFeedData}"/>
   </mx:VBox>
   
 </mx:TabNavigator>   

The application needs to pass details about the selected article when the LinkButton contained within the ItemRenderer is clicked. You could try and trap the position within the TileList, then read the necessary data from the ArrayCollection in turn switching tabs to reveal an instance of Adobe AIR's HTML control calling the URL of the article selected.

Each value stored within an ArrayCollection is an object, which in turn is passed into each instance of the ItemRenderer as an object called data. You are already accessing some of the properties of data in your ItemRenderer to display information in the TileList. Items within the TileList component are selectable, mouseOver and selected visual clues give feedback to users – this application will instead make use of a LinkButton for each article to give users a similar experience.

The click event of the ItemRenderer's LinkButton needs to be caught at the application level, either by the application itself or by the ItemRenderer's parent – the TileList. Your code will need to get access to the data for the row that the user had clicked in so that your application can fetch the appropriate URL.

Adding an EventListener to the TileList will allow you to catch any click that occurs on the component regardless of where the click has occurred. EventListeners only allow you to nominate an event and the method to handle that event, you cannot pass additional parameters like arguments required by the method.

Building a custom event handler

An excellent solution to the points listed above is to build a custom event handler and add it to the LinkButton in the ItemRenderer. The custom handler can have bubbling enabled so that it can be caught at different higher levels within the application, it can also have the capacity to pass arguments up to the location that it's handled.

Create a custom event class in a folder called events that extends the Event class. The class needs to contain the properties that you want to send with the custom event. Add a property to the class called article typed as Object as the application will need to process a few values later on.

Next create a constructor for the class that will take two arguments, the first typed as Object, the second typed as String. The constructor needs to call the super class to pass the type to it with an additional argument to allow bubbling. Populate the custom event class' article property with the parameter using the this prefix. Override the clone method of the super class to return a new instance of the custom event class, this is needed for event bubbling.

ReadArticleCustomClass.as:

package events
{
   import flash.events.Event;
   public class ReadArticleCustomClass extends Event
   {
      public var article:Object;
      
      public function ReadArticleCustomClass(articleParam:Object,type:String)
      {
         super(type,true);
         this.article = articleParam;
      }
      override public function clone():Event
      {
         return new ReadArticleCustomClass(article,type);
      }
   }
}

The custom event class will need to be declared in ItemRenderer.mxml where it will be used. Within the MetaData tag add two arguments: name argument in the code below is used to assign a name to the custom event; the type argument data types the custom event.

<mx:Metadata>
     [Event(name="readArticle",type="events.ReadArticleCustomClass")]
</mx:Metadata>

You will also need to add a handler to the LinkButton within the same file to dispatch the custom event.

Add a Script block below the Metadata tabs and import the custom event class. Then add a private method that will be called by the LinkButton's click event that creates an instance of the custom event class using the data object that you're populating the renderer's Text components with. Then dispatch the instance. Don't forget to add a call to the method to the LinkButton's click event.

<mx:Script>
   <![CDATA[
      
      import events.ReadArticleCustomClass;
      
      private function readArticleHandler():void
      {
         var eventObj:ReadArticleCustomClass = new ReadArticleCustomClass(data,"readArticle");
         dispatchEvent(eventObj);
      }
      
   ]]>
</mx:Script>

Switch to the Script block at the top of the application file, inside the block import the custom event class. Create a new private function that has no arguments and data type it to return a void, call it initApp and use it to add an event listener to the TileList to catch the custom event from the item renderer.

Associate the listener with a method called showArticle(). Call the initApp() function using WindowedApplication component's applicationComplete event. You will also need to create the private function showArticle(), have it accept an argument data typed to the custom event class, returning a void.

import events.ReadArticleCustomClass;
private function initApp():void
{
   FeedDisplay.addEventListener("readArticle",showArticle);
}

private function showArticle(event:ReadArticleCustomClass):void
{
}

Loading HTML content

The TabNavigator in your application presently has one tab, used to display a listing of articles fetched with the RSS feed. Add another VBox under the one holding the contents for the first tab. Like the Articles tab this VBox has a ProgressBar component nested inside a Box container, below the Box is another VBox that contains a HTML control.

From within MyAirRssReader.mxml:

 <mx:VBox label="View Article" 
   width="100%" height="100%" 
   verticalGap="0">
   <mx:Box id="HtmlLoader" 
      width="100%" height="30" 
      backgroundColor="#B8AF9C"
   paddingTop="6" paddingRight="10" paddingBottom="5" paddingLeft="10">
      <mx:ProgressBar id="htmlProgress" 
         indeterminate="true" 
         width="100%" 
         barColor="#B8AF9C"
         labelPlacement="center" 
         label="loading page"/>
   </mx:Box>         
   <mx:VBox width="100%" height="100%" 
      paddingTop="10" paddingRight="10" paddingBottom="10" paddingLeft="10" borderStyle="solid" borderThickness="1" 
      borderColor="#B8AF9C">
      <mx:HTML id="articleLoader" 
         complete="HtmlLoader.height=0" 
         width="100%" height="100%"/>
   </mx:VBox>
 </mx:VBox>

The HTML control is new to Adobe AIR, it's used to load and display HTML formatted text. Its location property is used to specify a URL, you can also use the htmlText property to load a HTML formatted string. The complete event can be used to broadcast that its contents are loaded. You will notice that the complete event is being used in the code above to minimize the height of the Box containing the ProgressBar.

The RSS feed supplied by MXNA contains two parameters that are URLs; the first, link, is a parameterized call to MXNA that passes the desired URL as an argument, this helps Adobe keep track of the links from their site that folk are using. The second rdf:about is a direct link to the original article. Your application will call the link parameter from the RSS feed, this will result in a redirect from the Adobe site to the original URL.

The showArticle() method that you added to the applications Script block needs to set the HTML controls location property to the URL of the article selected so that the page can be retrieved. If you attempt to set the location property and switch tabs on the TabNavigator you'll discover an error in your code. Because the HTML control is not yet visible it has not been initialized and your application will throw an error because you have attempted to populate a property of a non-existent object.

What you will do instead is create a variable to store the value of the URL that you want the HTML component to open with it's location property, then set that property to the required value when you display the HTML component. More desirable is to have the application only fetch a URL if it differs from what is presently displayed.

Create two bindable private properties: one to store the URL to Adobe to fetch the article; the other to store the original link so that the application will only fetch a new URL if you have selected a different LinkButton.

Modify the showArticle() method set the values of the two new properties – then switch tabs in the navigator. Create a new private method without parameters called showArticleTab(), have it return a void. Compare the URL loaded into the HTML component to what's been requested by comparing its location property against the new currentArticleLink property, if they don't match load the new currentArticle property by setting the location property of the HTML component.

The showArticleTab() method will be called by the show event of the VBox, this will fire when the "View Article" tab is selected. If you're loading a new URL you will need to reveal the ProgressBar for this tab by changing the height of the container that it sits in, add additional logic to the showArticleTab() method to conditionally change the height of the container.

You've now made all the necessary changes to the Script block in the application page; it should look like the sample code below:

 <mx:Script>
   <![CDATA[
   
      import mx.collections.ArrayCollection;
      import mx.rpc.events.ResultEvent;
      import events.ReadArticleCustomClass;
      
      [Bindable] private var rssFeedData:ArrayCollection;
      [Bindable] private var currentArticle:String;
      [Bindable] private var currentArticleLink:String;
      
      private function initApp():void
      {
         FeedDisplay.addEventListener("readArticle",showArticle);
      }
      
      
      private function showArticle(event:ReadArticleCustomClass):void
      {
         currentArticle = event.article.link;
         currentArticleLink = event.article["rdf:about"];
         ContentNavigator.selectedIndex = 1;
      }         
                                                   
      private function rssHandler(event:ResultEvent):void
      {
         rssFeedData = event.result.RDF.item;
         RssLoader.height=0;
      }
      private function showArticleTab():void
      {
         if (articleLoader.location != currentArticleLink) articleLoader.location = currentArticle; 
             articleLoader.loaded?HtmlLoader.height=0:HtmlLoader.height=30;
      }
               
   ]]>
 </mx:Script>

Modify the opening tag for the second tab's VBox container to use the show event to call the showArticleTab() method.

 <mx:VBox label="View Article" 
   width="100%" height="100%" 
   verticalGap="0"
   show="showArticleTab()">

The overall experience of the HTML control is that it takes some time to fetch and render a HTML page, it's methods and properties do not allow for the kind of feedback during operation that we're used to from a browser. The application that you have built is a simple solution that can be finessed in many ways, including error trapping for the processes that are making HTTP calls.

Charles (http://www.xk72.com/charles/) is a Java-based application that will run on Windows, Mac OS X, and Linux, developed for the monitoring and analysis of remote procedural calls it does this by capturing and interpreting HTTP traffic on your computer. It was used to test the application created for this article.

Packaging and compiling the application

If your testing has proven successful and you're satisfied with the functionality of the application the next thing is to do is to package and compile it for distribution.

The application descriptor file has settings for a number of Adobe AIR's visual features. Application transparency and the appearance of the window are two examples. Another is the ability to set icons for your application. An example of settings for icons is present amongst the blocks of comments in the descriptor file.

A number of graphics formats are supported but you will find that good results are achieved with PNGs that include an alpha channel. Copy the sample entries and remember to paste the code outside of the comment they're supplied in. Adobe AIR will scale supplied graphics for any of the missing sizes. You will need to select the images while using the Adobe AIR Deployment Export Wizard.

The last step is to compile your Adobe AIR application into an AIR file for deployment. Right click your project and choose Export, expand AIR and choose Adobe AIR Package. The next step is to choose the files necessary for to the AIR file. If your application is all Flex like the one that you have just built you will not need to select anything as the compiled SWF produced by Flex Builder will be used automatically by the Adobe AIR compiler. Extras like additional configuration files or icons specified in the descriptor file will need to be selected for inclusion.

The last thing to do is select the location for the AIR file to be saved and give the file a name.

Where to go from here

Authoring an Adobe AIR application in Flex isn't too different from building a Flex application. The developer must be mindful that small details like application dimensions are not part of the application tag; rather, they're part of an application configuration file.

Building an application that consumes HTML is a relatively simple thing: supply Adobe AIR's HTML component with a URL and it will fetch it, albeit a little slow right now – but this is a preview release. The sample application that you've been building doesn't have all the bells and whistles – there's rough edges here and there that need polishing, but you've also had opportunity to learn both Flex and Adobe AIR with insight into some intermediate concepts like custom event handlers.

Now go out and build Adobe AIR applications like crazy!

About the author

Trainer, mentor, developer, blogger, author and presenter, Andrew Muller has been involved with the production and development of Rich Internet Applications in Australia since 2002. The most certified Adobe instructor in the southern hemisphere, Andrew is also an Adobe Community Expert. Andrew has written articles for Internet.au, Digital Media World, Australian Developer and International Developer magazines; is a regular blogger and feature writer for BuilderAu.com.au; and has spoken at Macromedia/Adobe related conferences both in the United States, New Zealand and Australia. Andrew is senior designer/developer at webqem pty limited in Sydney.

Retrieved from "http://labs.adobe.com/wiki/index.php/AIR:Articles:Building_Your_First_Flex-based_Adobe_AIR_Application"