Chapter 14. Sending and Loading Data

<feature><title>In This Chapter</title> </feature>

Flash platform applications almost always use data in one form or another. Generally, applications need to have some form of client-server data communication. In the simplest example, an application might have to load plain text. For example, an application might have one responsibility: displaying the day’s news post. And some applications might have significantly more complex data communication requirements. For example, a more sophisticated version of the news application might need to retrieve an index of all the top news stories, the contents of news stories, user comments for each news story, and it might even incorporate live data that broadcasts the transcript of a live commentator.

There are many different types of data you can use with Flash Platform applications, including these:

  • Text (including Unicode support)

  • XML

  • AMF (Action Message Format, a binary messaging format)

  • Binary data

In addition to the many types of data, there are many ways you can transmit that data, including the following:

  • HTTP

  • RTMP

  • XML socket connection

  • Binary socket connection

In this chapter, we’ll look at each of these topics with the exception of the socket connection topics, which are outside the scope of this book. Then we’ll look at relevant design patterns and how they apply to sending and loading data.

Loading Text

One of the simplest ways to work with data is to load blocks of text either from a text file or from an Internet resource. Although you can certainly embed text in an SWF file, loading text at runtime has the following advantages:

  • You can manage when the text is loaded. When you embed text in an .SWF, the text loads as part of the SWF file. However, when you load text at runtime, you can load the text only when the application requires it. For large amounts of text, this can be a significant advantage.

  • Editing text files is generally easier than assembling strings with ActionScript. This is especially true when the text contains HTML elements or lots of non-standard Unicode characters.

  • Loading text at runtime allows you to update the context without having to recompile and redeploy a new .swf. This is especially true when the text is loaded from a dynamic resource such as a ColdFusion page where the content is retrieved from a Web service, a database, or some other source that is updated frequently.

Although you can load text with any of the techniques described in this chapter, this section focuses on the simplest mechanism for loading blocks of text. Using the flash.net.URLLoader class, you can load data from a URL. When you use URLLoader, you also must use the flash.net.URLRequest class to specify the URL from which you want to load the content. The following statement constructs a new URLRequest object that points to a text file called data.txt that is stored in the same directory as the SWF file:

var request:URLRequest = new URLRequest("data.txt");

You must then create a URLLoader object before you can load data using the URLRequest object. The URLLoader constructor does not require any parameters, as you can see here:

var loader:URLLoader = new URLLoader();

Note

Although it is not required, you can optionally pass a URLRequest object to a URLLoader constructor to begin a data request immediately.

URLLoader objects dispatch complete events when the data loads. For that reason, you’ll almost always want to register a listener for the complete event:

loader.addEventListener(Event.COMPLETE, onData);

You can then use the load() method to load the data. The load() method requires that you pass it a URLRequest object specifying the URL of the data to load, like this:

loader.load(request);

The load() method initiates the request for the data and works asynchronously. Flash Player does not wait for the response before continuing to execute the code. It simply makes the request; when the data loads, the URLLoader object dispatches events, including the complete event when the data has completely loaded.

When the complete event occurs, the URLLoader object notifies all listeners. The listeners receive an event parameter with a target property that references the URLLoader object dispatching the event. The URLLoader class defines a data property that contains the data that was loaded. The following event listener uses trace() to output the data that was loaded:

private function onData(event:Event):void {
   trace(event.target.data);
}

Next, we’ll look at a simple example that uses the Model View Controller (MVC) pattern (described in Chapter 3) to load and display four different limericks from text files. This example uses four text files called limerick0.txt, limerick1.txt, limerick2.txt, and limerick3.txt. The files contain the following text (each block of text is a different file):

There was an Old Man on a hill,
Who seldom, if ever, stood still;
He ran up and down,
In his Grandmother's gown,
Which adorned that Old Man on a hill.
- Edward Lear

There was a Young Lady whose chin,
Resembled the point of a pin;
So she had it made sharp,
And purchased a harp,
And played several tunes with her chin.
- Edward Lear

There was a Young Lady whose eyes,
Were unique as to colour and size;
When she opened them wide,
People all turned aside,
And started away in surprise.
- Edward Lear

There was a Young Lady of Bute,
Who played on a silver-gilt flute;
She played several jigs,
To her uncle's white pigs,
That amusing Young Lady of Bute.
- Edward Lear

Creating the LimerickData Class

Next, we’ll create a model class called LimerickData that essentially acts as a wrapper for a URLLoader object:

package com.peachpit.aas3wdp.limerickreader.data {
   import flash.events.EventDispatcher;
   import flash.net.URLLoader;
   import flash.net.URLRequest;
   import flash.events.Event;
   import flash.net.URLVariables;
   import flash.net.URLRequestMethod;
   import flash.net.URLLoaderDataFormat;

public class LimerickData extends EventDispatcher {

   private var _loader:URLLoader;
   private var _limerick:String;
   private var _ids:Array;

// Retrieve the current limerick string.
public function get limerick():String {
   return _limerick;
  }

public function LimerickData() {
  _loader = new URLLoader();
  _loader.addEventListener(Event.COMPLETE, onData);
  _limerick = "";
  // Store indices to use to reference each of the
  // text files.
  _ids = [0, 1, 2, 3];
 }

// Advance to the next limerick.
public function next():void {
  // Retrieve a random element from _ids.
  var index:uint = uint(_ids[Math.floor(Math.random() * _ids.length)]);

  // Load the text from one of the files.
  var request:URLRequest = new URLRequest("limerick" + index + ".txt");
  _loader.load(request);
  }

// The data has loaded. Set the limerick text, and dispatch
// an event to notify listeners.
private function onData(event:Event):void {
  _limerick = _loader.data;
  dispatchEvent(new Event(Event.CHANGE));

  }

 }

}

We’ll use the LimerickData class as a data model for the application. As the limerick changes, it will notify listeners.

Creating the LimerickView Class

The LimerickView class listens for change events dispatched by a model object, and it then draws itself using the data passed into the constructor:

package com.peachpit.aas3wdp.limerickreader.views {
   import flash.display.Sprite;
   import com.peachpit.aas3wdp.limerickreader.data.LimerickData;
   import flash.text.TextField;
   import flash.text.TextFieldAutoSize;
   import flash.events.Event;

public class LimerickView extends Sprite {

   private var _data:LimerickData;
   private var _textField:TextField;

   // Pass in the data model to use.
   public function LimerickView(data:LimerickData) {
     _data = data;

     // Listen for change events.
     _data.addEventListener(Event.CHANGE, draw);

     // Create the text field to use to display the 
     // limerick.
     _textField = new TextField();
     _textField.border = true;
     _textField.autoSize = TextFieldAutoSize.LEFT;
     addChild(_textField);
     draw();
}

// The data has changed, so display the new text.
public function draw(event:Event = null):void {
  _textField.text = _data.limerick;
}

}

}

This class simply creates a text field that displays the current limerick text value from the data model.

Creating the Main Class

The main class simply creates an instance of the model and an instance of the view and uses a timer to load a random limerick every five seconds:

package {

   import flash.display.Sprite;
   import com.peachpit.aas3wdp.limerickreader.views.LimerickView;
   import com.peachpit.aas3wdp.limerickreader.data.LimerickData;
   import flash.utils.Timer;
   import flash.events.TimerEvent;
   import flash.events.Event;

   public class LimerickExample extends Sprite {

      private var _data:LimerickData;

      public function LimerickExmmple() {
      _data = new LimerickData();
      var view:LimerickView = new LimerickView(_data);
      addChild(view);
      startTimer(null);
   }

   private function startTimer(event:Event):void {
       var timer:Timer = new Timer(5000);
       timer.addEventListener(TimerEvent.TIMER, onTimer);
       timer.start();
       onTimer();
   }

   private function onTimer(event:TimerEvent = null):void {
     _data.next();

   }
  }
}

This class creates an instance of both the model and the view. It uses a timer to advance the model to the next limerick every 5 seconds. When you run this code, you should see the limericks displayed on the screen, changing every 5 seconds.

Sending and Loading Variables

The preceding technique is appropriate when you want to simply load blocks of text from static URLs. However, often an application requires a greater degree of variability. In such cases, you need to send and/or load variables either by themselves or in conjunction with loading text.

Although there are lots of ways to send and load variables, the following sections look specifically at sending and loading URL-encoded variables using HTTP requests and responses through a URLLoader object, building on what we discussed in the preceding section regarding loading text.

Note

The URL-encoded format serializes values into a string that uses Web-safe characters. Variables are grouped into name/value pairs, which consist of the name of the variable and the value delimited by an equal sign. For example, a=1 is a name/value pair for a variable called a with a value of 1. If there are multiple name/value pairs, they are delimited by ampersands. For example, a=1&b=2 represents two variables: a and b.

Sending Variables

Sending variables requires that the resource receiving the request is capable of receiving the variables. For example, you can send variables to a PHP or ColdFusion page, but you cannot meaningfully send variables to a text file.

When you want to send variables, you have two basic options: appending the query string to the URL in the URLRequest object or using a URLVariables object.

When you construct a URLRequest object, you can simply append a query string to the URL. The following example constructs a URLRequest object that points to a URL with a query string:

var request:URLRequest = new URLRequest("data.php?index=0");

Sending data using this first technique has the advantage of being relatively simple to implement when the query string is simple. However, there are two primary disadvantages:

  • Adding lots of variables with dynamic values to the query string makes the code more difficult to read.

  • You can send the data only using HTTP GET.

The second technique uses a flash.net.URLVariables object. The URLVariables constructor does not require any parameters. After you’ve constructed a URLVariables object, you can assign arbitrary properties and values to the instance. Each property corresponds to a variable you want to send.

var variables:URLVariables = new URLVariables();
variables.a = 1;
variables.b = 2;

You can then assign the URLVariables object to the data property of the URLRequest object, like this:

var request:URLRequest = new URLRequest("data.php");
request.data = variables;

Regardless of which technique you use to assign the variables, you employ the load() method of a URLLoader object to send the request just as you would when loading text. The only difference occurs when you want to send the variables using POST rather than GET. URLRequest objects send all requests using GET by default. If you want to specify the method explicitly, you can use the method property and assign to it either the GET or the POST constant of the flash.net.URLRequestMethod class, like this:

request.method = URLRequestMethod.POST;
loader.load(request);

The following revision to the LimerickData class presented earlier in this chapter uses a PHP script as the request URL, and it sends two variables:

package com.peachpit.aas3wdp.limerickreader.data {
   import flash.events.EventDispatcher;
   import flash.net.URLLoader;
   import flash.net.URLRequest;
   import flash.events.Event;
   import flash.net.URLVariables;
   import flash.net.URLRequestMethod;
   import flash.net.URLLoaderDataFormat;

   public class LimerickData extends EventDispatcher {

      private var _loader:URLLoader;
      private var _limerick:String;
      private var _ids:Array;

      public function get limerick():String {
         return _limerick;
      }

      public function LimerickData() {
        _loader = new URLLoader();
        _loader.addEventListener(Event.COMPLETE, onData);
        _limerick = "";
        _ids = [0, 1, 2, 3];
      }
      public function next():void {
         var index:uint = uint(_ids[Math.floor(Math.random() * _ids.length)]);
         var variables:URLVariables = new URLVariables();
         variables.limerickIndex = index;
         variables.html = 0;
         var request:URLRequest = new URLRequest("http://www.rightactionscript.com/
Sending Variableslimerick/limerick.php");
         request.method = URLRequestMethod.POST;
         request.data = variables;
         _loader.load(request);
      }

      private function onData(event:Event):void {
          _limerick = _loader.data;
          dispatchEvent(new Event(Event.CHANGE));
      }
    
   }
}

Now the LimerickData class is configured to send a request with variables to a PHP script. The result is the same as before when it was loading data from four text files, but now it is able to point to a single PHP script.

Loading Variables

Not only can you send variables, you can also load variables. Loading variables differs from loading text only in how you ask Flash Player to interpret the return value. By default, Flash Player treats the data property of the URLLoader object as plain text. However, if you set the URLLoader.dataFormat property, you can specify that you want Flash Player to automatically attempt to decode the return value as variables. When that occurs, the data property is a URLVariables object, and you can simply retrieve the variable values by using the variable names.

To set the dataFormat property, use the VARIABLES constant of the flash.net.URLLoaderDataFormat class, like this:

loader.dataFormat = URLLoaderDataFormat.VARIABLES;

In the preceding examples, we’ve had to assume that there were four limericks, and we had to hard-code the indices that would return the limericks. In this example, we’ll first load variables from a PHP script to retrieve the valid indices. The PHP script returns a string in the following format: limerickIds=0,1,2,3. Next we’ll revise the LimerickData class so that it loads the data and parses it into an array before loading any of the limericks. Because the model cannot load the limericks until it has first loaded the variables, that introduces a dilemma: Currently, the main class calls the next() method immediately. There is no guarantee that the limerick IDs will have loaded before the next() method is called, and that will result in an unhandled error with the current implementation (because the _ids will be null.) You have two basic options:

  • Require a change to the main class so that it doesn’t call next() until the IDs have loaded. This option entails dispatching and listening for an additional event.

  • Change the implementation of next() so that it handles requests elegantly if the IDs haven’t yet loaded.

We’ll opt for the second option. The next() method will test that _ids is not null before trying to make a request to the server. We’ll add a property that keeps track of whether or not the next() method has been called. If that property is true, then the code will automatically call next() when the IDs load. Here’s the updated class:

package com.peachpit.aas3wdp.limerickreader.data {
   import flash.events.EventDispatcher;
   import flash.net.URLLoader;
   import flash.net.URLRequest;
   import flash.events.Event;
   import flash.net.URLVariables;
   import flash.net.URLRequestMethod;
   import flash.net.URLLoaderDataFormat;
   
   public class LimerickData extends EventDispatcher {

      private var _loader:URLLoader;
      private var _limerick:String;
      private var _idsLoader:URLLoader;
      private var _ids:Array;
      private var _pendingNext:Boolean;

      public function get limerick():String {
         return _limerick;
      }

      public function LimerickData() {
         _loader = new URLLoader();
         _loader.addEventListener(Event.COMPLETE, onData);
         _limerick = "";
         _idsLoader = new URLLoader();
         _idsLoader.addEventListener(Event.COMPLETE, setIds);
         _idsLoader.load(new URLRequest("http://localhost/limerick/limerickIndex.php"));
         _idsLoader.dataFormat = URLLoaderDataFormat.VARIABLES;

      }
         private function setIds(event:Event):void {
         _ids = _idsLoader.data.limerickIds.split(",");
         if(_pendingNext){
            next();
         }
       }

      public function next():void {
         if(_ids != null) {
            var variables:URLVariables = new URLVariables();
            variables.limerickIndex = _ids[Math.floor(Math.random() * _ids.length)];
            variables.html = 0;
            var request:URLRequest = new URLRequest("http://localhost/limerick/
               limerick.php");
            request.method = URLRequestMethod.POST;
            request.data = variables;
            _loader.load(request);
         }
         else {
            _pendingNext = true;
         }

       }

       private function onData(event:Event):void {
          _limerick = _loader.data;
          dispatchEvent(new Event(Event.CHANGE));
       }
     }

  }

Now the code loads limerick IDs from the PHP script first. After it loads the IDs, it can load limericks from the limerick PHP script just as it did previously. This configuration is much more flexible because it allows us to change the IDs of the limericks by changing the output of the limerickIndex.php script rather than having to recompile the SWF with new IDs.

Sending and Loading XML

XML (eXtensible Markup Language) is a standard for storing and transferring data. Flash Player has built-in support for working with XML data. You can read about working with XML in Chapter 15, “E4X (XML).” In this chapter, we’ll focus on how to send and load XML data.

When you send and load XML from Flash Player, it is always converted to a string. That enables you to send and load XML data (as a string) using the same techniques you use to send and load text. To send and load XML, you use the same URLLoader and URLRequest classes you used to send and load text.

Sending XML

When you send XML data, you generally send the raw XML data using HTTP POST. The ActionScript code required to accomplish this task is similar to that for sending variables. However, rather than using a URLVariables object, you simply assign the XML string to the data property of the URLRequest object. You then generally want to set the MIME type of the request using the URLRequest.contentType property. The following example sends an XML string to a PHP script:

var loader:URLLoader = new URLLoader();
var request:URLRequest = new URLRequest("saveSettings.php");
request.contentType = "text/xml";
request.method = URLRequestMethod.POST;
request.data = "<settings><email>[email protected]</email><phone>555-1212</phone></settings>";
loader.load(request);

Loading XML

Loading XML is exactly the same as loading text with one additional step after the data has loaded. The XML constructor allows you to pass it a string that the constructor will parse into the XML object. You can simply pass the value from the data property of the URLLoader object.

var xml:XML = new XML(_loader.data);

Note

You can optionally cast the data as XML rather than passing it to an XML constructor.

Some XML you load might have additional whitespace (carriage returns and tabs) added for human readability. Unless you tell Flash Player to watch for the extra whitespace, it will interpret whitespace as XML elements. That’s generally not the desired behavior. The simple solution is to set XML.ignoreWhitespace before parsing the string to the XML object, like this:

XML.ignoreWhitespace = true;
var xml:XML = new XML(_loader.data);

Using Web Services

Web services are a popular standard for client-server data communication. However, Flash Player has no built-in classes specifically designed for working with Web services. If you are using the Flex framework, you can employ the mx.rpc.soap.WebService class. In this book, we’re concerned primarily with solutions that rely exclusively on the Flash Player API. For a description of the WebService class, see the Flex documentation.

If you are interested in a Web services solution that does not rely on the Flex framework, you have two options:

  • Write your own Web services framework in ActionScript. Although Web services appear in many forms, one of the most popular uses SOAP, an XML-based protocol that is sent over HTTP. Writing a Web service framework requires sending and loading XML data using URLLoader along with a custom SOAP parser.

  • Make standard requests to a server-side script that makes the Web service calls. The server-side script can return URL-encoded data or XML data.

Note

Another option is to manually create and parse SOAP requests and responses. This is advisable for only very simple cases. You can see an example of this in Chapter 15.

The second option is often the best. One obvious reason that server-side Web service proxies are a good idea is that server-side languages generally have robust Web service capabilities that can work with a variety of protocols—not just SOAP. Server-side scripts can also generally parse Web service responses more quickly. They can relay the responses back to the client in a format that is more compact than SOAP (to limit the bandwidth overhead), and the server can even cache responses when applicable to limit the number of Web service requests.

Using Flash Remoting

Flash Remoting is a technology for making remote procedure calls from Flash Player to server-side services. The concept is similar to that of Web services: The client makes a request to a method that’s exposed using a server-side service. The request is serialized in a specific format, sent over HTTP (or HTTPS), and the response is likewise serialized and sent back to the client. This approach enables remote clients and services to interact to create integrated applications. Like Web services, Flash Remoting has the advantage of automatically serializing and deserializing to and from native data types.

However, there are significant differences between Flash Remoting and Web services. Following are a few of those differences:

  • Flash Remoting uses AMF (Action Message Format) as the protocol for packaging data. Although this might initially sound like yet another non-standardized, proprietary format, AMF is actually a binary form of SOAP that has a significant advantage over SOAP. SOAP packets have a lot of overhead that increases the bandwidth requirements. Over time, the extra bandwidth costs can add up. AMF can send the same amount of data in a much more compact (i.e., binary) format than its SOAP counterparts.

  • AMF is supported natively by Flash Player. In addition to its use with Flash Remoting, AMF is the format Flash Player uses for shared objects and local connections. Because AMF is supported natively in Flash Player, that means that serializations and deserializations to and from native ActionScript types is automatic and fast.

  • AMF support is not built into most server technologies. However, adding the necessary AMF gateway on the server is simple. Gateways are available for most major platforms including ColdFusion, Java, Perl, .NET, and PHP. There are even reliable and enterprise-ready open-source options available.

Understanding Flash Remoting Basics

When you want to use Flash Remoting, there are two elements that communicate: a client and a service. The service is generally a class that has been exposed so that it is available to Flash Remoting. Flash Remoting is a request-response model, which means the client must initiate all requests. The client must make all calls through an intermediary called a gateway. The gateway is a web-accessible resource on the server that is capable of accepting AMF requests, deserializing them, and delegating the requests to the appropriate services.

There are many Flash Remoting gateway products, including the following:

  • OpenAMF (www.openamf.org): an open-source Java gateway

  • WebORB (www.themidnightcoders.com): gateway products for .NET, Java, Ruby on Rails, and PHP

  • AMFPHP (www.amfphp.org): an open-source PHP gateway

Each gateway has its specific installation instructions. However, after you’ve installed the gateway, the general instructions to use it are the same. The following section looks at the ActionScript required to make Flash Remoting calls. In each case, you’ll need to know the URL to the gateway resource for your server. The documentation for the specific gateway you are using will tell you what you need to know to locate the correct resource. It is always a web-accessible resource such as a page (a PHP page, a .NET page, and so on) or a servlet.

Making Flash Remoting Calls

Although the Flex framework and the Flash Remoting components for Flash provide a high-level API for working with Flash Remoting, at a lower level, all Flash Remoting calls go through a flash.net.NetConnection object. In this section, we’ll look exclusively at working directly with NetConnection objects to make Flash Remoting calls.

The first step in making Flash Remoting calls is to construct a NetConnection object using the constructor. The constructor does not require any parameters, as you can see here:

var netConnection:NetConnection = new NetConnection();

Next, you must specify the gateway URL using the connect() method. Despite its name, the connect() method does not actually attempt to make a connection to the resource. It simply sets the gateway URL for the object so that subsequent Flash Remoting calls can be routed through the correct gateway resource.

netConnection.connect("http://localhost/gateway");

After you’ve set the gateway URL, you can make calls to methods of available services using the call() method. The call() method requires at least two parameters: the name of the service and method as a string, and an object indicating how to handle responses.

The name of the service and method must be the fully qualified service name and method name, all in dot notation. That means that if the service is in a package, you must specify the package as well as the name of the service. The name of the service and the name of the method are also separated by a dot. The following statement makes a call to the test method of the Example service:

netConnection.call("Example.test", null);

The second parameter can be null (as in the preceding example) if and when you do not need to listen for a response from the service method. However, if you do need to listen for a response, you can use a flash.net.Responder object. The Responder constructor requires that you specify two functions to handle the possible responses: a result and an error. The following statement makes a call to the test method, this time handling the response:

netConnection.call("Example.call", new Responder(onResult, onError));

When the service method returns a valid value, the result method gets called and is passed the return value as a parameter. When an error occurs, the error method gets called with an Object parameter whose properties detail the error.

You can also pass parameters to the service method by adding additional parameters to the call() method parameter list. For example, the following code passes the parameters 1, true, and “a” to the service method:

netConnection.call("Example.call", new Responder(onResult, onError), 1, true, "a");

The following rewrite of the LimerickData class uses Flash Remoting instead of loading data using URLLoader. For the sake of simplicity, this example does not handle errors. In an actual application, you might want to handle errors by bubbling up error events (as described in Chapter 13, “Working with Events”).

package com.peachpit.aas3wdp.limerickreader.data {
   import flash.events.EventDispatcher;
   import flash.events.Event;
   import flash.net.NetConnection;
   import flash.net.Responder;

   public class LimerickData extends EventDispatcher {

      private var _limerick:String;
      private var _ids:Array;
      private var _pendingNext:Boolean;
      private var _netConnection:NetConnection;

      public function get limerick():String {
        return _limerick;
      }

      public function LimerickData() {
        _limerick = "";

        // Use a NetConnection object rather than the 
        // URLLoader object used previously.
        _netConnection = new NetConnection();
        _netConnection.connect("http://www.rightactionscript.com/limerick/gateway.php");
        _netConnection.call("LimerickService.getIds", new Responder(setIds, null));
      }

      private function setIds(ids:Array
         _ids = ids;
         if(_pendingNext) {
           next();
         }
      }
      public function next():void {
         if(_ids != null) {
            var index:uint = _ids[Math.floor(Math.random() * _ids.length)];
            _netConnection.call("LimerickService.getLimerick", new Responder(onData,
               null), index, false);
         }
         else {
           _pendingNext = true;
         }
      }

      private function onData(limerick:String
        _limerick = limerick;
        dispatchEvent(new Event(Event.CHANGE));
      }

   }
 }

This version accomplishes the same basic tasks as previous versions. However, it now uses Flash Remoting to accomplish these goals.

Optimizing Data Communication

When you work with data, it is frequently important that you optimize the manner in which you work with the data. The following sections look at several specific ways you can optimize your work with data.

Caching Data

When an application makes calls to services, it is typically either submitting or requesting data. When an application requests data, there are two ways it can use that data: temporarily or persistently. If the data is used temporarily, it can and should be discarded when the application no longer needs it. However, when the data is used persistently, it can sometimes be useful to store that data in a persistent data model rather than discarding it and re-requesting it when the application needs the data again.

The technique of caching persistent data allows you to minimize the number of requests made to the server when the same data is used over and over. The sample limerick application is a good case in point. The application uses just a handful of pieces of data. The implementation thus far makes requests for each limerick every time the data is needed by the application. If the application displays one of the limericks fifty times, it also makes a request for the data fifty times from the server. Yet the cost of making those network requests is expensive, both in terms of bandwidth usage and latency in the application. In this particular case, a better solution is to store the limerick data client-side rather than making the request to the server each time.

There are two ways you can handle retrieving persistent data: requesting all the data at once or requesting the data one piece at a time as the user needs it, but caching the data rather than discarding it. The first technique has the advantage of ensuring the immediate availability of data when the user requests it. Yet the first technique also requires a potentially large initial download, even though the user may or may not be using all the data. The second technique allows the application to download data only when it is first requested by the user. Which technique you select depends on the requirements for the application. If the application allows for an initial wait for the user but requires low latency and immediate responsiveness after that initial wait, then you should download the data as part of an initialization. If the data set is very large and the average user of the application is not likely to use all the data every time she uses the application, it is generally better to download data on demand and cache it for later use.

We’ll look at how to rewrite the LimerickData class so that it uses the on-demand technique—caching data so the class doesn’t have to download the same data twice. For this purpose, we’ll introduce a new class called LimerickItem. This class is a simple data model for a single limerick that stores the limerick text and its index.

package com.peachpit.aas3wdp.limerickreader.data {
   import flash.events.EventDispatcher;
   import flash.events.Event;

public class LimerickItem extends EventDispatcher {

   private var _index:uint;
   private var _text:String;

   public function LimerickItem(index:uint) {
      _index = index;
   }

   public function getIndex():uint {
      return _index;
   }
    
   public function getText():String {
      return _text;
   }

   public function setText(value:String):void {
      _text = value;
      dispatchEvent(new Event(Event.CHANGE));
   }

  }
}

Next, modify the existing LimerickData class so that it stores LimerickItem objects in an array. When the application requests a limerick that’s already been loaded, the data model returns the cached data rather than request it from the server again.

package com.peachpit.aas3wdp.limerickreader.data {
   import flash.events.EventDispatcher;
   import flash.events.Event;
   import flash.net.NetConnection;
   import flash.net.Responder;
   
   public class LimerickData extends EventDispatcher {

      private var _limerick:String;
      private var _ids:Array;
      private var _pendingNext:Boolean;
      private var _netConnection:NetConnection;
      private var _limericks:Array;

      public function get limerick():String {
         return _limerick;
      }

      public function LimerickData() {
         _limerick = "";
         _netConnection = new NetConnection();
         _netConnection.connect("http://localhost/limerick/gateway.php");
         _netConnection.call("LimerickService.getIds", new Responder(setIds, null));

         // This is the array in which the model stores each
         // of the LimerickItem objects.
         _limericks = new Array();
       }

       private function setIds(ids:Array):void {
          _ids = ids;
          if(_pendingNext) {
             next();
          }
       }

       public function next():void {
         if(_ids != null) {
           var index:uint = _ids[Math.floor(Math.random() * _ids.length)];

           // Test if the data has already been
           // requested. If not, request it. If so,
           // set current limerick to the cached
           // value.
           if(_limericks[index] == undefined) {
              var item:LimerickItem = new LimerickItem(index);
              // Listen for the change event that
              // the item dispatches when the value
              // is set, and then call onItem().
              item.addEventListener(Event.CHANGE, onItem);
              _limericks[index] = item;
              // Use item.setText as the result 
              // event handler method.
              netConnection.call("LimerickService.getLimerick", new Responder
              (item.setText, null), index, false);
            }
            else{
                onData(_limericks[index].getText());
            }
          }
          else {
             _pendingNext = true;
          }
         }
        // When the data is returned and assigned to the limerick
        // item, then call onData() wuith the data.
        private function onItem(event:Event):void {
           onData(event.target.getText());
        }

        private function onData(limerick:String):void {
            _limerick = limerick;
            dispatchEvent(new Event(Event.CHANGE));
        }
      
      }
    }

Queuing and Pooling Requests

When you have lots of service calls, there are two ways you can call them: back to back, or all together. The back-to-back technique is what we call queuing. The technique of calling all the methods at once is what we call pooling. Each has advantages and disadvantages.

A novice approach to remote procedure calls is to simply make the calls when it’s convenient. This approach means that some calls are queued and some calls are pooled, but never intentionally. This is not generally a good approach because there are cases in which calls must be queued and cases in which it is better if they are pooled. It is better to specifically select the manner in which you make service method calls.

The limerick application has a perfect example of a case in which method calls must be queued. Two service methods are called in the example: getIds() and getLimerick(). It is essential that getIds() is called before getLimerick() because getLimerick() requires a parameter whose value is determined by the result of the getIds() method call. This means you must queue the method calls to ensure that getIds() returns a value before getLimerick() is called. In the existing example, this order is achieved by using an if statement to test that the _ids array (which is populated by the return value from getIds()) is defined before calling getLimerick().

Pooling requests is rarely if ever required. However, pooling does significantly reduce network overhead. If there are several method calls that do not have dependencies (which would require that they be queued), and if those methods are likely to be called in relatively close succession anyway, it is better to make the requests all at the same time. Flash Player automatically bundles together all requests made in the same frame (which basically amounts to all requests made in the same routine) into a single AMF request packet.

Summary

Working with data is integral to the majority of Flash Platform applications. In this chapter, you learned how to load text, send and load variables, work with the transfer of XML, and use Flash Remoting. You also learned techniques for optimizing your work with data.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.146.152.71