Google analytics code

Thursday, October 29, 2009

Flex: Problems reusing the HTTPService class

UPDATE: This problem occurred because of a bug in the 3.4 SDK. This bug does not exist in 3.2 or lower. I've opened a bug with adobe on the issue.

UPDATE 2: The issue has been resolved in a new nightly build of the SDK. It should be fixed when 3.6 is release.

I ran into an issue using the HTTPService object that involved removing event listeners that were added inside MXML.

Here is a snippet of code in the project. This will open an xml file and load some data into a List.

//this will fetch all our content.
private var _httpService:HTTPService = new HTTPService; 

private function init() : void {
    _httpService.url = "stub/data/albums.xml";
    _httpService.resultFormat = "e4x";
    
    //set up http call
    _httpService.addEventListener(ResultEvent.RESULT, populateList);
    _httpService.send();
}

private function populateList(event:ResultEvent) : void {
    //set the data provider
    albumsComboBox.dataProvider = event.result.album;
    
    //remove listener
    _httpService.removeEventListener(ResultEvent.RESULT, populateList);
}

This will open another xml file and populate the TileList with images.

private function loadAlbum(event:ListEvent) : void {
    _httpService.url = "stub/data/" + event.currentTarget.selectedItem.@images;
    _httpService.resultFormat = "e4x";
    
    //set up http call
    _httpService.addEventListener(ResultEvent.RESULT, populateTilelist);
    _httpService.send();
}

private function populateTilelist(event:ResultEvent) : void {
    //set the data provider
    imageGrid.dataProvider = event.result.photo;
    
    //remove listener
    _httpService.removeEventListener(ResultEvent.RESULT, populateTilelist);
}

When loadAlbum calls send() it will call populateList first and then goto populateTilelist. removeEventListener didn't work. I posted a question to StackOverflow with the problem and got some great answers.


To get around this problem I created a HTTPService wrapper that can be reused with no issues because it's an ActionScript Class. It's very light weight, doesn't include everything under the sun, but it gets the job done in a pinch. This is the 3rd revision of the class. It's been cleaned up and there are fewer methods now.

package util
{
    import mx.controls.Alert;
    import mx.rpc.AsyncResponder;
    import mx.rpc.AsyncToken;
    import mx.rpc.IResponder;
    import mx.rpc.events.FaultEvent;
    import mx.rpc.events.ResultEvent;
    import mx.rpc.http.HTTPService;
    
    public class HTTPServiceWrapper {
        //reference for connections
        private var _httpService:HTTPService;
        
        private var _alertTitle:String = "HTTPService: An error occured";
        
        // Holds all the tokens and callbacks
        private var _processingQueue : Object = {};
        
        /**
         * Create the HTTPService 
         * 
         */
        public function HTTPServiceWrapper() : void {
            //create the service
            _httpService = new HTTPService;
        }
        
        /**
         * Get content from URL 
         * @param url
         * @param resultFormat
         * @param callBack
         * @param request
         * @param returnErrorEvent
         * 
         */
        public function getContent(url:String,
                                    resultFormat:String,
                                    callBack:Function,
                                    request:Object = null, 
                                    returnErrorEvent:Boolean = false) : void {
            var asyncToken:AsyncToken;
            var internalIResponder:IResponder;
            
            //set the url and result format
            _httpService.url = url;
            _httpService.resultFormat = resultFormat;
            _httpService.request = null;
            
            //check for key/value pairs to be sent in the url
            if(request) {
                _httpService.request = request;
            }
            
            //send the request
            asyncToken = _httpService.send();
            internalIResponder = new AsyncResponder(onGetContentHandler,onFault, asyncToken);
            asyncToken.addResponder(internalIResponder);
            
            //give token unique ID
            asyncToken = tokenID(asyncToken);
            
            _processingQueue[asyncToken.ID] = new QueueObject(callBack,returnErrorEvent);
        }
        
        /**
         * This is the handler event for getContent. The callBack should accept a ResultEvent
         *  
         * @param event
         * @param token
         * 
         */
        private function onGetContentHandler(event:ResultEvent, token:AsyncToken) : void {
            var queueObject:QueueObject = _processingQueue[token.ID];
            
            queueObject.callBack(event);
        }
        
        
        /**
         * This is the fault event for the class. The callBack should accept a null ArrayCollection and a FaultEvent if
         * you want to handle the error.
         *  
         * @param event
         * @param token
         * 
         */
        private function onFault(event:FaultEvent, token:AsyncToken) : void {
            var queueObject:QueueObject = _processingQueue[token.ID];
            
            //handle the fault
            if(queueObject.returnErrorEvent){
                queueObject.callBack(null, event);
            }
            else {
                var displayMessage:String = event.fault.faultString;
                displayError(displayMessage);
                queueObject.callBack(null);
            }
        }
        
        
        /**
         * This will add a unique identifier to a token 
         * @param token
         * @return 
         * 
         */
        private function tokenID(token : AsyncToken) : AsyncToken {
            token.ID = Math.random();
            return token;
        }
        
        /**
         * Displays the error on a fault event in the content service 
         * @param event
         * 
         */
        private function displayError(displayMessage : String) : void {
            Alert.show(displayMessage, _alertTitle, Alert.OK);
        }
    }
}

class QueueObject{
    public var callBack:Function;
    public var returnErrorEvent:Boolean;
    
    public function QueueObject(callBack:Function, returnErrorEvent:Boolean){
        this.callBack = callBack;
        this.returnErrorEvent = returnErrorEvent;
    }
}


Using the class is really simple. Use getContent and pass it the file you want to load, the format it should be loaded in and the call back you want it to call after the file is retrieved. There are two extra properties I'm not using right now, but they will be helpful in the future. The fourth parameter accepts an Object of key/value pairs to be sent with the URL.The fifth parameter lets you capture the result of an error if one occurs. The call back should accept a FaultEvent object if you want to capture the error. If you let HTTPServiceWrapper handle the error an Alert window will pop up showing the error.
//http wrapper
private var _httpServiceWrapper:HTTPServiceWrapper = new HTTPServiceWrapper;

/**
* Runs when the application loads
* */
private function init() : void {
    //get the list of albums
    _httpServiceWrapper.getContent("stub/data/albums.xml", "e4x", populateList);
    
    //set event listeners for app
    albumsComboBox.addEventListener(ListEvent.CHANGE, loadAlbum);
}

/**
* This will populate the album list
* */
private function populateList(event:ResultEvent) : void {
 //if the result is null don't set it
 if(!event) {
  return;
 }
 
 //set the data provider
 albumsComboBox.dataProvider = event.result.album;
}

/**
* This will fetch the selected album
* */
private function loadAlbum(event:ListEvent) : void {
    _httpServiceWrapper.getContent("stub/data/" + event.currentTarget.selectedItem.@images, "e4x", populateTilelist);
}

This cut quite a bit of bloat out of my app and is also very reusable which saves time in the long run. The final version of this code is hosted here. You can view source on it by right-clicking in the app and selecting "View Source". Please let me know if you have any questions about it.

5 comments:

  1. This is great I love it (kc litts).

    ReplyDelete
  2. The class has a problem when you reuse it and call like this


    var url_params:Object = new Object();
    url_params.param1 = "OLD PARAM";

    _httpServiceWrapper.getContent("test2.php", "e4x", results2, url_params);
    NOTICE I ADDED A ARGUMENT url_params


    //NEW CALL HERE
    _httpServiceWrapper.getContent("test1.php", "e4x", results1);

    No Params needed for this page

    looking at the passed parameters I see OLDPARAM being passed still even though I specified no params.

    the problem is here in your httpservice class
    if(request) {
    _httpService.request = request;
    }

    Needs to be .......

    if(request) {
    _httpService.request = request;
    } else {
    _httpService.request = "";
    }

    ReplyDelete
  3. Didn't realize there was an error with the request object. I've updated my code to take care of that. Thanks for the notice.

    ReplyDelete
  4. I'm using Flash Builder 4.0.1 but I need to work on a project that created on Flex 3.3 some months ago. I can't convert the project to FB4 because we still have another PC with the older Flex 3 and we need the option to switch the development between these two machines.
    Well... turns out that my FB4 came with the Flex 4.1 and the Flex 3.5 SDKs. And the f*ckin' 3.5 SDK is also buggy! The removeEventListener statement does not work with the HTTPService object.
    So, I'm downloading the 3.6.0.16736 nigthly build SDK. Hope this fixes the problem :(

    ReplyDelete
  5. According to adobes bug log it should be fixed in 3.6. Good luck.

    ReplyDelete

If you found this page useful, or you have any feedback, please leave a comment.