Remote Synthesis
Search my blog:
Viewing By Entry / Main
Feb 01, 2008

AIR Basics - Building My First AIR Application

I have been intending on diving into Adobe AIR since it was released, but up until now I mainly developed the sample applications that come with the O'Reilly pocket guides. Well, as I hinted in this week's open source update, I finally got around to building a legitimate (even if simple) AIR application. While this application is specifically designed for me, as it helps me build the open-source update lists for each week (saving me potentially hours of time formatting the content), building it required some use of features in AIR like accessing the file system and calling remote REST API's (in this case for Google Notebook). Therefore, I figure I will share how I built it to help some of you who may be in the same situation (i.e. wanting to get started with AIR but not sure where to begin). As a brief caveat, I won't pretend that everything I do here is best practice, it was a "quickie" app that has an intended audience of one. However, if those of you who know Flex and AIR better than I do have some suggestions for improvement, please do share.Basic File System Access
The primary goal of this application was to connect to the Google Notebook REST API to pull a list of available notebooks (they must be public first) and then all the items within a specific notebook. The first thing you need to be able to do this is to get the user's Google ID that is the key to assembling any of the API URLs. When you make a notebook public, you will be given a URL along the lines of http://www.bloglines.com/sub/http://www.google.com/notebook/feeds/
15420494164259897982/notebooks/BDQJESgoQsO3K3Pci whereby the 15420494164259897982 represents your user ID. You can then use the URL http://www.bloglines.com/sub/http://www.google.com/notebook/feeds/15420494164259897982/ to get all the notebooks for a particular user. Therefore, I wanted to parse this user ID out of a public notebook URL and store it as part of the application settings permanently. I decided to do this via the file API in AIR to save it to a simple text file; here's how...

When the application finishes loading, I call a function I called "initApp()". I check the application storage directory for a text file called userid.txt, check to see if it exists then I check to see if it has any contents. If it does, then I read the contents in as the user ID. If the file does not exist or is empty, I load a modal pop-up to prompt the user for a public notebook URL. Let's look at the contents of initApp() to see how this is done.

private function initApp():void { CursorManager.setBusyCursor(); var createFile:Boolean = true; // start by checking if the users id is saved to text file var file:File = File.applicationStorageDirectory; file = file.resolvePath("userid.txt"); // if no file exists or is empty prompt for public URL if (file.exists == true) { var fileStream:FileStream = new FileStream(); fileStream.open(file, FileMode.READ); if (fileStream.bytesAvailable > 0) { createFile = false; fileStream.close(); readUserId(); } } if (createFile == true) { var urlPanel:UrlPanel = new UrlPanel(); mx.managers.PopUpManager.addPopUp(urlPanel,mainLayout,true); mx.managers.PopUpManager.centerPopUp(urlPanel); } }

The code for readUserId(), which simply opens a fileStream and reads the contents of the text file is pretty simple.

public function readUserId():void { var file:File = File.applicationStorageDirectory; file = file.resolvePath("userid.txt"); var fileStream:FileStream = new FileStream(); fileStream.open(file, FileMode.READ); userId = fileStream.readUTF(); fileStream.close(); loadNotebookUrl(); }

Calling Remote REST API's
Calling a REST service is basically just an HTTP Get or Post (with some URL variables appended perhaps). In this case, the API is very simple since I am not adding or modifying the contents of a notebook, just reading it. The methods here are not AIR specific, but would be the same for any Flex application. One note however, was that I did not build in the check to see if I was connected to the Internet, since this application had an audience of one, I am relying on the user to be smart enough to figure this out (though, I have my doubts).

The loadNotebookUrl() method calls the full list of public notebooks for a particular user via a standard HTTP Get. When this is returned I simply loop through the XML structure and populate a Notebook value object with the necessary values and append it to an array. On my screen, I have a ComboBox that is bound to the value of this array.

public function loadNotebookUrl():void { notebookListUrl = "http://www.google.com/notebook/feeds/" + userId; urlRequest = new URLRequest(notebookListUrl); urlRequest.method = "GET"; loader = new URLLoader(); loader.addEventListener(Event.COMPLETE, notebookListLoadedHandler); try { loader.load(urlRequest); } catch (error:Error) { trace("Unable to load Google Notebook list."); } }

Calling the API to get the contents of a particular notebook was slightly more complicated because by default it only returned the most recent 10 records and it was not obvious how to get the rest. The only manner I found was by appending a "start-index" query parameter and incrementing this by 10. Thus, for a list containing 40 records, I would make 4 calls to the API. I can determine if we are done by retrieving the total number of results from the XML returned via the API; I compare this to a variable used as a counter to establish the start-index. As the results are returned I append them as populated "Entry" value objects to an array which is bound to a DataGrid.

The loadEntries() method is called by the callback function from the HTTP get. It simply loops through the XML and populates the entry beans and then determines if we need to call the API again with the incremented start-index. This is my first experience (at least in a while) parsing XML in ActionScript, so forgive me if this is bad technique. Basically, I check the name of the node to determine what to set it to.

public function loadEntries(parent:XMLNode):void { // we don't remove all here since they are appended 10 at a time var node:XMLNode = parent.firstChild; var totalResults:Number = 10; do { if (node.localName == "entry") { var entry:Entry = new Entry(); entry.title = node.childNodes[4].firstChild.nodeValue; entry.content = node.childNodes[5].firstChild.nodeValue; entry.url = node.childNodes[6].attributes.href; entries.addItem(entry); } else if (node.localName == "totalResults") { totalResults = Number(node.firstChild.nodeValue); } node = node.nextSibling; } while (node != null); CursorManager.removeBusyCursor(); startIndex += 10; if (startIndex < totalResults) { callNotebookURL(); } }

When you double-click on an entry I open up another modal pop-up that allows you to modify the properties of the entry; pretty basic Flex stuff.

Outputting the Text File
The final piece of this application is that once you modify every entry, assigning it to a project and a category and such, the application will write it out to a text file on the desktop in the format of my typical Open-Source Update entry. This is done by simply looping through the array of entries (they are sorted after each entry is edited so they are in the proper order at all times). Finally, all I needed was to create a large string with the contents and write it out via a string buffer (again, since this has an audience of one, I forgo allowing you to specify a location and a filename).

private function saveToFile():void { var file:File = File.desktopDirectory; file = file.resolvePath("newpost.txt"); var fileStream:FileStream = new FileStream(); fileStream.open(file, FileMode.WRITE); var fileText:String = ""; for(var i:Number=0; i<entries.length; i++){ if (i == 0 || entries[i].category != entries[i-1].category) { fileText += "<h3>" + entries[i].category + "</h3>\n\n"; } if (i == 0 || entries[i].project != entries[i-1].project) { fileText += "<strong>" + entries[i].project + "</strong>\n"; } fileText += "<a href='" + entries[i].url + "'>" + entries[i].title + "</a>
<br />\n"
; fileText += entries[i].content + "\n\n"; } fileStream.writeUTF(fileText); fileStream.close(); Alert.show("File written to desktop"); }

Final Touches
The final item was simply to modify the applications XML and create an AIR installation file. The application, from a very basic standpoint, does what I need it to do. Its missing a lot of bells and whistles, but I did manage to get my feet wet in AIR (hmm...that sounds impossible actually...maybe feet dry?). Going forward, I think I could add some functionality to do things like save my progress on a particular Notebook or prompt for filename and location, but for now this should be a great time-saver and it really only took a handful of hours to build (even with limited knowledge of AIR). I will say though that once you build something in AIR, you feel compelled to do more.

Comments
Joshua Curtiss
Cool. I'll be doing my first &quot;official&quot; AIR project later this month. Thx for the real-world mini-intro.


Write your comment



(it will not be displayed)