Mike Chambers

code = joy

URLLoader subclass with automatic refresh support

with 16 comments

Chris Hayen twittered to me this morning expressing his wish that the ActionScript 3 URLLoader class had a refreshInterval property. I responded that this should be pretty simple to add by extending the class. Well, I had a little time while I download some internal software builds, so I put together a class that adds a refreshInterval property.

This has not been tested very much. In fact, I have only tested when the data loads successfully from the server. However, I wanted to post an early / rough version here with the hope that I get feedback on it. If the consensus is that this could be useful, then I will look at making it more solid, and adding it to the as3corelib library.

The class adds two new public APIs:

  • refreshInterval Number is milliseconds on how often the data should be loaded.
  • callIsActive Boolean that indicates whether the instances is currently in the process of loading data from the server.

Here is the class:

package
{
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.TimerEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.utils.Timer;

	/*
	 *	Class that adds an option to automatically have the data reloaded at
	 *	specified intervals.
	 */
	public class URLLoader2 extends URLLoader
	{
		private var _refreshInterval:Number = 0;
		private var timer:Timer;
		private var lastRequest:URLRequest;

		//make this public
		private var _callIsActive:Boolean = false;

		/*
			Constructor
		*/
		public function URLLoader2(request:URLRequest=null)
		{
			//store last request used
			lastRequest = request;

			//register for events 
			addListeners();

			//call base class constructor
			super(request);
		}

		/********** public setters / getters **************/

		/*
			Refresh interval in milliseconds.
			
			If equal to or less than 0, data will not be reloaded.
			
			note: currently refresh timer starts from the time that the request
			goes to the server, not from when the request changes. Should this 
			change?
		*/
		public function set refreshInterval(value:Number):void
		{
			_refreshInterval = value;
		}

		public function get refreshInterval():Number
		{
			return _refreshInterval;
		}

		//read only, whether the class is currently in process of loading data
		//from server
		public function get callIsActive():Boolean
		{
			return _callIsActive;
		}

		/************* public methods ***************/

		//overriden load method
		public override function load(request:URLRequest):void
		{
			//might need to listen for some of the status codes
			stopTimer();

			//store the last request so we can reuse it
			lastRequest = request;

			//call load to make the request
			super.load(request);

			//start the timer for the reload
			startTimer();

			//set that the class is in the process of communicating with the server
			_callIsActive = true;
		}

		//overriden close method
		public override function close():void
		{
			stopTimer();
			_callIsActive = false;
			super.close();
		}

		//private api to stop the timer
		private function stopTimer():void
		{
			if(timer != null)
			{
				//clear the timer. We need to do this instead of reuse it
				//in case the refreshInterval  changes the next time we use it.
				//although we could potentially check that when we start the
				//timer
				timer.stop();
				timer == null;
			}
		}

		//private api to start the timer
		private function startTimer():void
		{
			//make sure the timer is stopped firest
			stopTimer();

			//if call to server is active, then dont refresh
			//should we queue this up?
			//if refresh interval is less than or equal to zero dont refresh
			if(_refreshInterval <= 0 || _callIsActive)
			{
				return;
			}

			//create new timer instance
			//note, we could check if the existing timer instance can be used
			timer = new Timer(_refreshInterval);
			timer.addEventListener(TimerEvent.TIMER, onTimer);

			//start the timer
			timer.start();
		}

		//adds listeners for loading events
		private function addListeners():void
		{
			addEventListener(Event.COMPLETE, onComplete);
			addEventListener(IOErrorEvent.IO_ERROR, onIOError);
			addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
		}

		/*********** private event handlers ******************/

		//event hander when timer interval is fired
		private function onTimer(e:TimerEvent):void
		{
			//callIsActive should never be true here, but should we check it
			//anyways?

			//load the last request
			load(lastRequest);
		}

		//called when data is succesfully loaded
		private function onComplete(e:Event):void
		{
			_callIsActive = false;
			startTimer();
		}

		//called if there is an error loading data
		private function onIOError(e:IOErrorEvent):void
		{
			_callIsActive = false;
			startTimer();
		}

		//called if there is a security error loading data
		private function onSecurityError(e:SecurityErrorEvent):void
		{
			_callIsActive = false;
			startTimer();
		}
	}
}

Here is a simple example of using the class (it is pretty much the same as using URLLoader):

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.URLRequest;

	public class URLLoaderRefreshTest extends Sprite
	{
		public function URLLoaderRefreshTest()
		{
			var r:URLRequest = new URLRequest("http://feeds.feedburner.com/MikeChambers/");

			var u:URLLoader2 = new URLLoader2();
				u.addEventListener(Event.COMPLETE, onComplete);
				u.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
				u.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);

				u.refreshInterval = 3000;

				u.load(r);
		}

		private function onComplete(e:Event):void
		{
			trace("oncomplete");
		}

		private function onIOError(e:IOErrorEvent):void
		{
			trace("onioerror");
		}

		private function onSecurityError(e:SecurityErrorEvent):void
		{
			trace("onsecurityerror");
		}

	}
}

Couple of notes:

  • The class needs a better name. Any suggestions?
  • Class is currently in default package
  • Currently, the refresh countdown starts at the beginning of the data load, and not the end. Thoughts?
  • Even if refreshInterval is 0 and refreshing not being used, I keep references to data loading events. This is small bit of extra overhead (in cases where data is not being refreshed), but simplifies the code.
  • If a refresh interval is reached and the instance is still communicating with the server, the refresh will be skipped.
  • Class has had very little testing, especially in cases where an error occurs from loading the data (so there are probably some bugs and logic errors).

So, if you have any suggestions for improvements, find any bugs, or think it is (or isnt) useful, post them in the comments.

Written by mikechambers

September 12th, 2008 at 1:41 pm

Posted in General

16 Responses to 'URLLoader subclass with automatic refresh support'

Subscribe to comments with RSS or TrackBack to 'URLLoader subclass with automatic refresh support'.

  1. I think you will find that you will end up retrieving the same data over and over from the browser’s cache, at least that is my experience with similar code. You’ll need to adjust the URL on each load such as adding a random number as GET parameter to get around the browser cache.

    Jared

    12 Sep 08 at 2:24 pm

  2. For a name, how about URLReLoader?

    I think counting down at the beginning of data load is good. That way I know if I say “reload every 30 seconds”, I’ll get new data roughly every 30 seconds, not every 31 or 32 seconds (if it takes 1 or 2 seconds to load).

    And maybe I’m misreading the code, but it seems like if the timer triggers before the data finishes loading, then it will start another load before the previous one has finished, which is probably not what you want.

    load() — calls startTimer(), and starts the data loading
    onTimer() — called by the timer, even if the data hasn’t finished loading yet? and then calls load() again.

    David R

    12 Sep 08 at 2:36 pm

  3. How about URLPoller?

    It would be interesting to think about ways that the class could modify the request with each load, e.g. for APIs that require a last modified date, or have paging or something like that.

    Tom Carden

    12 Sep 08 at 2:39 pm

  4. @Jared

    Good point on the browser caching. I could append a random number to the end of the request, or maybe futz with the headers.

    Although it does make me a little nervous messing with the request, as it could have unintended consequences for the developer.

    mike chambers

    mesh@adobe.com

    mikechambers

    12 Sep 08 at 3:20 pm

  5. @Tom

    Yeah. I was thinking about this a little. I.e., it could pass the request index to the server, or something like that.

    Again though, i get a little nervous messing with the request, as it could have some unintended consequences (that could be difficult for the developer to debug)

    mike chambers

    mesh@adobe.com

    mikechambers

    12 Sep 08 at 3:21 pm

  6. didn’t test it yet but, reading the code, I noticed what i think is a writing error
    in the stopTimer function you wrote timer == null; instead of timer = null; so you probably want to correct it ;)

    cheers
    Alessandro

    Cyberpunk

    12 Sep 08 at 3:28 pm

  7. @Alessandro

    Good catch. I fixed it locally.

    Thanks…

    mike chambers

    mesh@adobe.com

    mikechambers

    12 Sep 08 at 3:54 pm

  8. Hey thanks Mike. I was going to write this but I was stuck peeling a bucket of shrimp :)

    Haven’t gotten a chance to play with it yet, but it would be good to have a property to bind for parameters. The reason I wanted this in the first place was for the Twitter client I’m writing. Right now I have timers running to request the user’s timeline every 60 seconds. Currently I have to stop the timer when the request is made and start it back up after the result comes back so I don’t get cached on my second request(the twitter api will cache if you request again before the 60 seconds is up). I’m going to play with it and see if I can add a good way to set the id of the last comment pulled.

    Thanks again.

    Chris Hayen

    12 Sep 08 at 4:50 pm

  9. [...] His blog post on it [...]

  10. Hey Mike,

    I wrote something like this for one of my clients a while ago. I don’t own the rights otherwise i would distribute.

    My approach was a bit different. What I did instead of extending loader was that I wrote a LoaderManager class that allowed you to set refresh intervals as well as a slew of other features, per project requirements. anyhow this is how the refresh would look:

    LoaderManager.refresh(loaderInstance:Loader, interval:Number, appendTimeStap:Boolean);

    I also had a cancelRefresh method, a refreshCount prop and broadcasted an event if the data was different thenn what was previously loaded (part of the managers responsibilities).

    I agree with Tom’s suggestion, I like URLPoller.

    -erik

    erikbianchi

    13 Sep 08 at 11:32 pm

  11. Hello Mike,
    I think you can use the existing timer instance instead of creating a new one:

    // call initTimer() from constructor
    private function initTimer():void
    {
    timer = new Timer(_refreshInterval);
    timer.addEventListener(TimerEvent.TIMER, onTimer);
    }

    private function stopTimer():void
    {
    if(timer != null)
    {
    timer.stop();
    }
    }

    private function startTimer():void
    {
    stopTimer();

    if(_refreshInterval <= 0 || _callIsActive)
    {
    return;
    }

    timer.delay = _refreshInterval;

    timer.start();
    }

    igor.ruzanov

    15 Sep 08 at 2:17 am

  12. Hi,

    I had a couple of thoughts on this.

    You could include a callback to provide the dynamic part of the URL (or a function which developers override in a subclass)

    Initially my usage would be for a URLLoader that keeps calling until it gets a successful return value, as a a kind of failsafe so that the user doesn’t have to keep requesting data. Although I guess this might just be done via a registered complete handler which cancels once it receives the right data.

    Joc

    Joc

    15 Sep 08 at 2:58 am

  13. I would suggest dispatching a custom event “REFRESH_INTERVAL” or something, so you can listen for that timer every time its executed.. Just a suggestion.

    dzedward

    15 Sep 08 at 4:17 pm

  14. [...] URLLoader subclass with automatic refresh support (from Mike Chambers) [...]

  15. [...] urlloader-subclass-with-automatic-refresh-support [...]

  16. Mike: Hey.. love your work and it’s helping me along my way.

    I am pretty naive, but this looks like it might be the start of supporting something like a mule server.. or ESB, enterprise service bus. has this been done?

    harley

    20 Nov 08 at 10:08 pm

Leave a Reply