Personal tools

Views

AIR:Articles:Taking Adobe AIR Applications Offline

From Adobe Labs

By John C. Bland II (http://www.johncblandii.com)

One of the greatest abilities of AIR, in my opinion, is the ability to create an application to run online and offline. The application could allow the user to make changes to their account, content, etc. while not connected and sync the data online when the connection returns. The user will only love the application even more.

In this article, I'm going to deal with how to manage your application in an offline and online state. The focus will be on managing network state and handling basic data versus how to develop AIR applications and/or Flex 2.0.1 applications. I will keep all data simple (XML) and only demonstrate retrieving online data and managing it offline.

Keep in mind that all code is from AIR Alpha 1 and what I write is my approach and isn't the "book" on how to go about things, although I may think it's a great way to approach it. Take what you read here and apply your own muscle to it.

Table of contents
[edit]

Requirements

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

Sample files: john_bland_sample_code.zip (1.83 MB)

Prerequisites: Working knowledge of Flex 3, MXML, and ActionScript 3.0.

[edit]

Detecting network state

I'm going to skip right to Flex Builder and dig into code. For more information on setting up Flex Builder and building Flex applications, visit the IDEs section of the Flex Developer Center.

The AIR Alpha 1 release provides a lovely event (Event.NETWORK_CHANGE) which notifies the application when the network state changes. This event does not tell you whether your application is online or off, it tells you that the network status has changed: you may have gone offline, gone online, logged into a VPN system, etc.

Let's start with the base code to capture the NETWORK_CHANGE event.

 [Code (AIROffline_Step1.mxml)]
 <?xml version="1.0" encoding="utf-8"?>
 <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="init()">
   <mx:Script>
 <![CDATA[
         private function init():void{
            Shell.shell.addEventListener(Event.NETWORK_CHANGE, onNetworkChange);
         }
         
         private function onNetworkChange(event:Event):void{
            trace(event);
         }
      ]]>
   </mx:Script>
 </mx:WindowedApplication>
 [/Code] 

This block of code is the bare minimum for network detection. Once the application's creationComplete event fires the init() method is called which adds an event listener to the shell and sets the listener function to onNetworkChange. Add a breakpoint to the trace() statement line of code and run this in debug. Once the application is open, disconnect from your network connection and you will see Flex Builder move the application to the breakpoint (see Figure 1).

Image:John_bland_fig01.jpg

Figure 1. Application breakpoint

[edit]

Determining online status

You now have an application that tells you when the network state changes. Now you need to know the current status (online or offline). We're going to try and load our online data. If we can, we're online. If not, we'll assume we're offline. Let's add a little code to detect status. Note that a future build of AIR is expected to make most of this code unnecessary.

 [Code (AIROffline_Step2.mxml)]
 <?xml version="1.0" encoding="utf-8"?>
 <mx:WindowedApplication    xmlns:mx="http://www.adobe.com/2006/mxml" 
                  layout="absolute" creationComplete="init()">
   <mx:Script>
      <![CDATA[
         [Bindable]
         private var isOnline:Boolean = false;
         private var request:URLRequest = new URLRequest("http://blogs.katapultmedia.com/jb2/_dev/onlineoffline/data/rooms.xml");
         private var requestLoader:URLLoader = new URLLoader();
         
         private function init():void{
            requestLoader.addEventListener(Event.COMPLETE, requestCompleteHandler);
            requestLoader.addEventListener(IOErrorEvent.IO_ERROR, requestErrorHandler);
            Shell.shell.addEventListener(Event.NETWORK_CHANGE, onNetworkChange);
            requestLoader.load(request);
         }
         
         private function onNetworkChange(event:Event):void{
            isOnline = !isOnline
            trace("Connected to Internet? " + isOnline);
         }
         
         private function requestErrorHandler(event:IOErrorEvent):void{
            isOnline = false;
         }
         
         private function requestCompleteHandler(event:Event):void{
            isOnline = true;
         }
      ]]>
   </mx:Script>
 </mx:WindowedApplication> 
 [/Code] 

We added a few things to the code. First let's briefly cover the flow.

  1. Application initializes
  2. Add event listeners for successful and unsuccessful data loads as well as network connection changes
  3. Try loading XML file (or whatever file/data load you need)

At this point the application is set up and ready to figure out connection state. Our listener functions will change the status of the isOnline variable accordingly. Notice the onNetworkChange() method merely changes isOnline to the opposite value. A better way of handling this would be to make a subsequent call to the server to ensure connection state.

There are multiple concerns with the current NETWORK_CHANGE event. This event is called when you go from online to offline, as noted above, but will also fire when you connect to VPN, etc. Keep in mind that the server used to double-check online status could go offline, which could make the data load fail, thereby tricking the application to show an offline state. Look for the networking updates in later builds to smooth this process.

For the sake of this article, we're going to take the best-case scenario (server is running fine and there are no other connection types) but this should not be seen as the way to approach an application. When future AIR builds, with better network support, are available, be sure to rewrite/update your code to properly handle more than the "best case scenario" (as noted above).

Run the above code in debug mode and watch the Console view as you connect and disconnect your Internet connection (see Figure 2).

Image:John_bland_fig02.jpg

Figure 2. Console view as you connect and disconnect

The next step is to build the application around the connection state.

[edit]

Creating a state-minded application

We know the connection state now and merely need to have the application respond accordingly. Here you will bind the currentState property of your application to the isOnline variable to achieve the desired effect and/or flow.

[edit]

Changing application states

I'm going to assume you're familiar with binding in Flex 2 so I'll breeze by the basics. What we'll do is make the application switch to a State based on isOnline being true or false.

 [Code (snippet; AIROffline_Step3.mxml)]
 <?xml version="1.0" encoding="utf-8"?>
 <mx:WindowedApplication    xmlns:mx="http://www.adobe.com/2006/mxml" 
                  layout="absolute" creationComplete="init()"
                  currentState="{isOnline ? 'Online' : 'Offline'}">
 [/Code]

All you should focus on is the currentState line. It shows us changing to the Online or Offline state based on the isOnline variable. Easy enough, right? Now let's create the states.

 [Code (snippet; AIROffline_Step3.mxml)]
   <mx:states>
      <mx:State name="Online">
         <mx:SetProperty name="status" value="Online"/>
      </mx:State>
      <mx:State name="Offline">
         <mx:SetProperty name="status" value="Offline"/>
      </mx:State>
   </mx:states>
 [/Code]

In each of the states we're merely changing the WindowedApplication.status value to show the current state. For you graphically capable folks you probably wanted me to show some really pretty network connection graphic that is grey or full color based on the state. Well, I'm graphically challenged so a status bar change is all you get.

So, what next? I'm glad you asked. Let's deal with the data.

[edit]

Managing data

We have our connection state as well as indicators to tell us the current state. Now we need to deal with the data. We will revisit the onNetworkChange() function and a few others. Let's first look at our direction and desired result.

Most of the code for this is already in place so I'll show snippets so you can see the code changes more clearly.

 [Code (snippet; Script block; AIROffline_Step4.mxml)]
 import flash.filesystem.*;
         private var localFile:File = File.applicationStorageDirectory.resolve("AIROffline/rooms.xml");
         private var localFileStream:FileStream;
 ...[other code]
 private function init():void{
            requestLoader.addEventListener(Event.COMPLETE, requestCompleteHandler);
            requestLoader.addEventListener(IOErrorEvent.IO_ERROR, requestErrorHandler);
            
            Shell.shell.addEventListener(Event.NETWORK_CHANGE, onNetworkChange);
            
            //create/open local file
            localFileStream = new FileStream();
            localFileStream.open(localFile, FileMode.UPDATE);
            localFileStream.close();
            
            loadData();
         } 
 [/Code]

The AIR for Adobe Flex Developers Pocket Guide is a great reference for file management so I won't cover things in detail here. What happens is a FileStream object is created pointing to the localFile resolved at variable declaration. FileMode.UPDATE will open the file or create it depending on whether or not it exists. If the file doesn't exist, the file will be empty. The file is then closed since all we want to do is make sure the XML file exists for later.

[Code (snippet; AIROffline_Step4.mxml)]
//Cleanup Methods
            private function applicationClosingHandler(event:*):void{
               localFileStream.close();
            }
         
         //Data Methods
            private function loadData():void{
               requestLoader.load(request);
            }
            
            private function readLocalFile():void{
               localFileStream.open(localFile, FileMode.READ);
               roomsXML = XML(localFileStream.readUTFBytes(localFileStream.bytesAvailable));
               localFileStream.close();
            }
            
            private function saveDataLocally():void{
               localFileStream.open(localFile, FileMode.WRITE);
               localFileStream.writeUTFBytes('<?xml version="1.0" encoding="utf-8"?>\n'+roomsXML.toXMLString());
               localFileStream.close();
            }
            
         //Connection methods
         private function onNetworkChange(event:Event):void{
            isOnline = !isOnline
            if(isOnline){
               loadData();
            }
         }
         
         private function requestErrorHandler(event:IOErrorEvent):void{
            isOnline = false;
            //Get data from local file
            readLocalFile();
         }
         
         private function requestCompleteHandler(event:Event):void{
            isOnline = true;
            roomsXML = XML(requestLoader.data);
            //Write data locally
            saveDataLocally();
         } 
[/Code]

I added an applicationClosingHandler() function only to make sure the FileStream is closed when the application closes. Most of the above code is familiar to you already. Notice the addition of the readLocalFile() and saveDataLocally() functions. These functions are used to manage the local file/data. The steps are as follows:

If isOnline == true
# requestCompleteHandler() called after the initial data load.
# Store data in roomsXML variable; application display updated automagically due to bindings
# saveDataLocally() called
## Save the data in roomsXML to the FileStream
If isOffline == false
# requestErrorHandler() called
# readLocalFile() called
## set roomsXML to the contents of the local roomsXML file

We only need to refresh the local data when online so inside of onNetworkChange() we need to call loadData() again, only if isOnline is true. Once the data loads we'll repeat the steps above for "If isOnline == true" which will save our local data as well as reset the roomsXML data to the data loaded online and subsequently update the display.

I added a few more items to the display so we could see the data change.

 [Code]
 <mx:VBox width="100%" height="100%">
      <mx:Text text="{roomsXML.Room.length()} Rooms Available"/>
      <mx:TextArea id="RoomsList" width="100%" height="100%" text="{roomsXML.toXMLString()}" selectable="false" editable="false"/>
   </mx:VBox>
 [/Code]

Currently the XML structure, as seen below, is online, for me at this moment. Figure 3 shows the results of running the application with this data.

 [Code (data/rooms.xml)]
 <?xml version="1.0" encoding="utf-8"?>
 <Rooms>
   <Room name="Room 1" />
   <Room name="Room 2" />
   <Room name="Room 3" />
   <Room name="Room 4" />
   <Room name="Room 5" />
   <Room name="Room 6" />
   <Room name="Room 7" />
   <Room name="Room 8" />
   <Room name="Room 9" />
   <Room name="Room 10" />
 </Rooms>
 [/Code] 

Image:John_bland_fig03.jpg

Figure 3. Running the application with this XML data

Now, I'm going to disconnect from my network, close the application, then reopen it (see Figure 4).

Image:John_bland_fig04.jpg

Figure 4. Reopening the application

OK, nothing sweet to see there, right? Only thing that changed was the status but let me close the application then disconnect my network connection after updating the XML online.

Here is the new XML (just duplicated Room nodes).

 [Code (data/rooms.xml)]
 <?xml version="1.0" encoding="utf-8"?>
 <Rooms>
  <Room name="Room 1"/>
  <Room name="Room 2"/>
  <Room name="Room 3"/>
  <Room name="Room 4"/>
  <Room name="Room 5"/>
  <Room name="Room 6"/>
  <Room name="Room 7"/>
  <Room name="Room 8"/>
  <Room name="Room 9"/>
  <Room name="Room 10"/>
  <Room name="Room 1"/>
  <Room name="Room 2"/>
  <Room name="Room 3"/>
  <Room name="Room 4"/>
  <Room name="Room 5"/>
  <Room name="Room 6"/>
  <Room name="Room 7"/>
  <Room name="Room 8"/>
  <Room name="Room 9"/>
  <Room name="Room 10"/>
  <Room name="Room 1"/>
  <Room name="Room 2"/>
  <Room name="Room 3"/>
  <Room name="Room 4"/>
  <Room name="Room 5"/>
  <Room name="Room 6"/>
  <Room name="Room 7"/>
  <Room name="Room 8"/>
  <Room name="Room 9"/>
  <Room name="Room 10"/>
 </Rooms>
 [/Code]

Now, I'm opening the application again, still Offline, so it will look like Figure 4 after loading the local data saved after the previous data load. Here comes the sweet spot. I'm going to reconnect to my network without closing the application. The online data file will be downloaded and saved locally (see Figure 5).

Image:John_bland_fig05.jpg

Figure 5. Online data file downloaded and saved locally

Sweet! Keep in mind the application never closed. I kept it open and just reconnected. If this doesn't wet your whistle of excitement, I don't know what will!

To finish the cycle, I'm going to disconnect from my network then open the application again. The result is Figure 5 with a state of Offline. Yummy!

OK: Everyone at the same time, "AIR IS SWEET!" Let's look at what we covered today.

[edit]

Where to go from here

From the ground up we built a quick application that monitored network status and responded to the status accordingly. We also setup a data sync so the user would always have the latest online data locally without having to request it. Our data was simple XML but you can see how this process could be utilized to store specific user information offline application use while in a disconnected state. The possibilities of how your application functions offline is 100% up to you. AIR gives you the tools and leaves it to you to be creative.

Again, keep in mind this is with AIR Alpha 1. I used some, what will become, unnecessary techniques. Keep an eye out for future releases of AIR. For now, enjoy Alpha 1!

I hope this article was helpful. If you have any questions and/or concerns, feel free to email me (mailto:john-articles@katapultmedia.com) and I'll try to respond within 24 hours. My apologies in advance for the spam trap.

Download AIR (when available), game-plan your application, and get rocking! Let AIR guide you on a path to a highly usable online/offline application.

Final code:

[Code]
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication    xmlns:mx="http://www.adobe.com/2006/mxml" 
                  layout="absolute" creationComplete="init()"
                  currentState="{isOnline ? 'Online' : 'Offline'}"
                  closing="applicationClosingHandler(event)">
   <mx:Script>
      <![CDATA[
         import flash.filesystem.*;
         
         private var localFile:File = File.appStorageDirectory.resolve("AIROffline/rooms.xml");
         private var localFileStream:FileStream;
         
         [Bindable]
         private var isOnline:Boolean = false;
         private var request:URLRequest = new URLRequest("http://blogs.katapultmedia.com/jb2/_dev/onlineoffline/data/rooms.xml");
         private var requestLoader:URLLoader = new URLLoader();
         [Bindable]
         private var roomsXML:XML = new XML();
         
         private function init():void{
            requestLoader.addEventListener(Event.COMPLETE, requestCompleteHandler);
            requestLoader.addEventListener(IOErrorEvent.IO_ERROR, requestErrorHandler);
            
            Shell.shell.addEventListener(Event.NETWORK_CHANGE, onNetworkChange);
            
            //create/open local file
            localFileStream = new FileStream();
            localFileStream.open(localFile, FileMode.UPDATE);
            localFileStream.close();
            
            loadData();
         }
         
         //Cleanup Methods
            private function applicationClosingHandler(event:*):void{
               localFileStream.close();
            }
         
         //Data Methods
            private function loadData():void{
               requestLoader.load(request);
            }
            
            private function readLocalFile():void{
               localFileStream.open(localFile, FileMode.READ);
               roomsXML = XML(localFileStream.readUTFBytes(localFileStream.bytesAvailable));
               localFileStream.close();
            }
            
            private function saveDataLocally():void{
               localFileStream.open(localFile, FileMode.WRITE);
               localFileStream.writeUTFBytes('<?xml version="1.0" encoding="utf-8"?>\n'+roomsXML.toXMLString());
               localFileStream.close();
            }
            
         //Connection methods
         private function onNetworkChange(event:Event):void{
            isOnline = !isOnline
            if(isOnline){
               loadData();
            }
         }
         
         private function requestErrorHandler(event:IOErrorEvent):void{
            isOnline = false;
            //Get data from local file
            readLocalFile();
         }
         
         private function requestCompleteHandler(event:Event):void{
            isOnline = true;
            roomsXML = XML(requestLoader.data);
            //Write data locally
            saveDataLocally();
         }
      ]]>
   </mx:Script>
   <mx:states>
      <mx:State name="Online">
         <mx:SetProperty name="status" value="Online"/>
      </mx:State>
      <mx:State name="Offline">
         <mx:SetProperty name="status" value="Offline"/>
      </mx:State>
   </mx:states>
   
   <mx:VBox width="100%" height="100%">
      <mx:Text text="{roomsXML.Room.length()} Rooms Available"/>
      <mx:TextArea id="RoomsList" width="100%" height="100%" text="{roomsXML.toXMLString()}" selectable="false" editable="false"/>
   </mx:VBox>
</mx:WindowedApplication>
[/Code]
[edit]

About the author

John C. Bland II is CEO and Chief Developer for Katapult Media, Inc. (http://www.katapultmedia.com), an Arizona based software and web development company. Along with running Katapult, he manages the Arizona Flash Platform User Group (http://www.gotoandstop.org). All titles aside, John is a geek.

Retrieved from "http://labs.adobe.com/wiki/index.php/AIR:Articles:Taking_Adobe_AIR_Applications_Offline"