Creating a DataManager in ActionScript 3.0 for Flex 2.0

I’ve updated the DataManager with a fix for concurrency issues over here.

When building Flex applications, I like to centralize data access, to remove the need for mx:WebService tags to be sprinkled throughout the application.  While the FAST data services helped out in this respect for Flex 15, it is not yet available for Flex 2 (and I’ve found porting it over manually to be a non-trivial task).

What I’ve created is a Singleton class for the DataManager, which will allow 1 and only 1 instance to be created for each unique WSDL.  Developers call a "makeRemoteCall" method on the appropriate instance, and pass it the name of the method to be called, as well as a uniquely named event that will be invoked when the results are back.

2/3/05 Note – The code below has been updated to run in the recently released beta 1

package managers {
import flash.events.EventDispatcher;
import mx.rpc.soap.WebService;
import mx.rpc.events.ResultEvent;
import mx.rpc.events.FaultEvent;
import mx.rpc.AbstractOperation;
import events.DataManagerResultEvent;
import flash.util.*;
/**  DataManager - singleton class which enforces only
a single object is created for eachwsdl.  To
access DataManager, use getDataManager(wsdl:String) */
public class DataManager extends EventDispatcher {
private var ws:WebService;
private var eventName:String;
// hashmap of instances for each wsdl  
private static var instanceMap:Object = new Object();
public function DataManager(pri:PrivateClass, wsdl:String){
this.ws = new WebService();
ws.wsdl = wsdl;
ws.loadWSDL();
ws.useProxy = false;
}
public static function getDataManager(wsdl:String):DataManager{
if(DataManager.instanceMap[wsdl] == null){
DataManager.instanceMap[wsdl] = new DataManager(new PrivateClass(),wsdl);
}
var dm:DataManager= DataManager.instanceMap[wsdl];
if(dm.ws.canLoadWSDL()){
return dm;
} else {
throw new Error("BAD WSDL:"+wsdl);
}
}
public function makeRemoteCall(methodName:String,eventName:String, ...args:Array):void{
this.eventName = eventName;
var op:mx.rpc.AbstractOperation = ws[methodName];
ws.addEventListener("result",doResults);
ws.addEventListener("fault",doFault);
if(args.length >0){
op.send.apply(null,args);
} else {
op.send();
}
}
private function doResults(result:ResultEvent):void{
var e:DataManagerResultEvent = new DataManagerResultEvent( eventName, result.result);
this.dispatchEvent(e);
}
private function doFault(fault:FaultEvent){
this.dispatchEvent(fault);
}
public override function toString():String{
return "DataManager";
}
}
}
/**  PrivateClass is used to make   DataManager constructor private */
class PrivateClass{
public function PrivateClass() {
}
}

This class starts simply enough, with a package declaration (I’ve set this to be in a managers.* directory), followed by imports for each of the classes I’ll use here (AS3 requires explicit imports of all classes).

Next the class is defined as a subclass of EventDispatcher (a fairly lightweight class, which instantiates the framework for broadcasting events).  Three private properties are then declared, ws to hold an instance of the WebService class; eventName which is the name of the event to be broadcast when results are received; and a static property instanceMap, which holds a HashMap (just an Object in AS) of instances of the web services.

Since AS3 doesn’t currently have private constructors (I’m still hoping this gets added into the language before its released), the constructor takes an instance of PrivateClass as an argument.  Since PrivateClass is defined outside of the package declaration, its only available within this same file.  This ensures that the constructor can not be called externally, essentially giving us a private constructor.  The other argument to the constructor is the WSDL for the WebService to use.  Internally, the constructor creates an instance of the WebService class, sets the wsdl, and calls loadWSDL() to initially load the WSDL (this is required any time you manually instantiate WebServices in ActionScript, but happens automatically when you use the WebService MXML tag.)

Next, the public static method getDataManager is defined.  This takes a WSDL URL as an argument.  Internally, this method determines if a WebService instance already exists for that WSDL, or if it needs to be created.  Either way, a handle to the WebService is generated, it validates that it can retrieve the WSDL (using the canLoadWSDL() method) and returns the appropriate instance (or throws a run time error if it can’t retrieve the wsdl)

The instance method makeRemoteCall is defined next, which takes a minimum of 2 arguments: the name of the remote method, and the name of the event to be broadcast when results are retrieved.  Any extra arguments passed in (as indicated by the … args:Array) will be passed on to the remote method.  An AbstractOperation instance is created to refer to the method on the remote object.  Event Listeners are added for results and faults, and the operation is triggered.  Note, the conditional statement determines if there is data to be passed to the server or not, if so, the apply() method is used to use the args array as a series of arguments (see this entry for details on apply), otherwise, the method is called with no arguments.

The two event handlers follow.  If results are successfully returned, the doResults method fires.  This creates an instance of the DataManagerResultEvent, and dispatches it.  If a fault is returned, it is simply re-dispatched.

Here is the definition of the DataManagerResultEvent:

package events {
import mx.rpc.events.ResultEvent;
import flash.events.Event;
import flash.util.*;
public class DataManagerResultEvent extends Event {
public var result:Object;
public function DataManagerResultEvent(type:String,result:Object){
super(type);
this.result = result;
}
public override function clone():Event{
return new DataManagerResultEvent(type, result);
}
}
}

This is is simple Event subclass, which holds the result object.  We didn’t need a class like this back in the Flex 1.5/AS2 days, as events were not strongly typed, but in the strongly typed world of AS3, I find myself creating more custom Event classes.

Finally, we can test it with a MXML page.  Here is the tester:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application  xmlns:mx="http://www.macromedia.com/2005/mxml"  creationComplete="initApp()">
<mx:Script>
<![CDATA[
import mx.rpc.events.FaultEvent;
import managers.DataManager;
import flash.events.Event;
import events.DataManagerResultEvent;
import mx.controls.Alert;
private var catManager:DataManager;
private var prodManager:DataManager;
private function initApp():void{
// get data managers    
var catWsdl:String = "http://www.flexgrocer.com/cfcs/category.cfc?wsdl";
var prodWsdl:String = "http://www.flexgrocer.com/cfcs/prodByCategory.cfc?wsdl";
catManager = DataManager.getDataManager(catWsdl);
prodManager= DataManager.getDataManager(prodWsdl);
// setup event listeners    
catManager.addEventListener("catsRetrieved",handleCats);
catManager.addEventListener("fault",showFault);
prodManager.addEventListener("prodsRetrieved",handleProds);
prodManager.addEventListener("fault",showFault);
//get categories        
catManager.makeRemoteCall("getCategories","catsRetrieved");
}
private function getProdsForCat(event:Event){
var catId:int = List(event.currentTarget).selectedItem.CATEGORYID;
prodManager.makeRemoteCall("getProdsByCategory","prodsRetrieved",catId);
}
private function handleProds(event:DataManagerResultEvent){
prods.dataProvider = event.result;
}
private function handleCats(event:DataManagerResultEvent){
cats.dataProvider = event.result;
}
private function showFault(event:FaultEvent):void{
Alert.show("Web Service Error: n "+event.fault.description);
}
]]>
</mx:Script>
<mx:List id="cats"
labelField="CATEGORY"
change="getProdsForCat(event)"/>
<mx:DataGrid id="prods"
width="100%"/>
</mx:Application>

If you have the Flash Player 8.5 installed, you can see this up and running here (source code can be obtained from a right click on that page). 

Tien Nguyen  created a variation on this to use RemoteObject for connecting to CFC’s with the CFAdapter.  When I have more time, I’ll work on creating a class which can make use of both

There are no comments.

Leave a Reply