Tutorial – Consuming REST web services in ActionScript 3 – Part 3

Hi Everyone,

This is the third installment of the series, you can find the first two here:
Part 1 – http://blog.martinlegris.com/?p=87
Part 2 – http://blog.martinlegris.com/?p=90

Today I will develop on the data handling once the data is received from the webservice calls.
I will describe some methodologies I’ve developped that help mainstream data handling by using custom data types to hold the data, and custom iterators to access these data, and custom event types to transport the data.

Starting Point

In the last tutorial, we had a Singleton class calls WSClient which ran the request for us, then dispatched an event once the xml data was received.

Let’s look at a sample XML file returned by a theoretical webservice. The webservice returns a list of songs contained in a particular playlist. Here is the structure for each song:

[xml]

6428

Darlingside
http://panther.music.com/media1/7d7/5/19/15/f/46578a1d6dd25.mp3
http://music.com/music/darlingside/lucky-you
http://panther.music.com/media1/7d7/5/19/15/f/46578a1defb3a.jpg
http://panther.music.com/media1/7d7/5/19/15/f/46578a1defb3a-thb.jpg
audio
4.54
[/xml]

First thing to do here, is to create a data object to hold this data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package data
{
	public class TrackData
	{
		protected var _id:Number;
		protected var _title:String;
		protected var _creator:String;
		protected var _location:String;
		protected var _info:String;
		protected var _image:String;
		protected var _tn:String;
		protected var _type:String;
		protected var _rating:Number;
 
		public function TrackData(id:Number = -1, title:String = "", creator:String = "", location:String = "", info:String = "", image:String = "", tn:String = "", type:String = "", rating:Number = 0)
		{
			_id = id;
			_title = title;
			_creator = creator;
			_location = location;
			_info = info;
			_image = image;
			_tn = tn;
			_type = type;
			_rating = rating;
		}
	}	
}

Now let’s add getter/setters for the properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
		// ...
		protected var _rating:Number;
 
		public function get id():Number { return _id; }		 
		public function set id(value:Number):void { _id = value; }
		public function get title():String { return _title; }		
		public function set title(value:String):void { _title = value; }		
		public function get creator():String { return _creator; }	
		public function set creator(value:String):void { _creator = value; }
		public function get location():String { return _location; }		
		public function set location(value:String):void { _location = value; }		
		public function get info():String { return _info; }		
		public function set info(value:String):void { _info = value; }		
		public function get image():String { return _image; }		
		public function set image(value:String):void { _image = value; }
		public function get tn():String { return _tn; }		
		public function set tn(value:String):void { _tn = value; }
		public function get type():String { return _type; }		
		public function set type(value:String):void { _type = value; }		
		public function get rating():Number { return _rating; }
		public function set rating(value:Number):void { _rating = value; }
 
		public function TrackData(....)

Now let’s create a function which will convert the XML into our TrackData object.. I usually like to put this function inside of the data object itself, and make it static. I like to call it “fromXML”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
		// ...
 
		public static function fromXML(xml:XML):TrackData
		{
			var data:TrackData = new TrackData();
			data.id = parseInt(xml.id.text());
			data.title = xml.title.text();
			data.location = xml.location.text();
			data.info = xml.info.text();
			data.image = xml.image.text();
			data.tn = xml.tn.text();
			data.type = xml.type.text();
			data.rating = parseFloat(xml.rating.text());
 
			return data;
		}
 
		// ....

To use this function we would do like so :

1
2
3
 
var xml:XML = XML(evt.target.data as String);
var trackData:TrackData = TrackData.fromXML(xml);

And tada, we have “digested XML” to play with, a data object which is hard typed and easy to manipulate using auto-completion.

The Feed’s Signature

Now, since in this case we are receiving a list, or sequence of songs contained inside a playlist, it would be nice to have a custom iterator to let us easily access the songs. You can also put them in an array, but since arrays in AS3 are not type’able, (they will be in AS4), I like to create custom iterators that I call feeds. Our feed will have the following signature:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 
package feeds
{
	public class TrackFeed
	{
		// access to it's pointer
		public function get pointer():Number
 
		// access to the length of the trackfeed
		public function get length():Number
 
		// the constructor, which receives xml..
		public function TrackFeed(data:XML)
 
		// some of the standard iterator functions
		public function first():TrackData
		public function next():TrackData
		public function last():TrackData
		public function previous():TrackData
 
		// to get a specific one..
		public function getAt(index:Number):TrackData
 
		// other functions which could be useful
		public function getById(id:Number):TrackData
 
		// and in this case, a playlist could have a name, an author and a type
		public function get name():String
		public function get author():String
		public function get type():String
 
	}
}

Basically, what I call a Feed is an Iterator that has been adapted to a specific data type, and which lets you iterate through a list of data objects. In this case, I use it to parse XML from a webservice response and enable easy access to that data.

XML Data to be parsed

Our werservice returns a playlist. Here is what the xml looks like

[xml]

MN Tech Picks
Music Nation

6428

Darlingside
http://panther.music.com/media1/7d7/5/19/15/f/46578a1d6dd25.mp3
http://music.com/music/darlingside/lucky-you
http://panther.music.com/media1/7d7/5/19/15/f/46578a1defb3a.jpg
http://panther.music.com/media1/7d7/5/19/15/f/46578a1defb3a-thb.jpg
audio
4.54


[/xml]

There are two techniques I’ve identified to handle the data inside of the Feed. Either transform all the data right away to custom data types, or keep the data as XML and convert it on demand. I will, in this example, show the second method. I has proven to cause problems on large data sets, for example when receiving 80k + XML files from the Amazon Web Services. So if you do have problems, just convert everything to AS3 objects and don’t keep a reference to the XML.

The Feed’s Implementation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
 
package feeds
{
	import data.TrackData;
 
	public class TrackFeed
	{
		protected var _pointer:Number = 0;
		protected var _feed:XML;
 
		public function get length():Number
		{
			return _feed.trackList.track.length();
		}		
 
		public function get pointer():Number
		{
			return _pointer;
		}		
 
		public function TrackFeed(data:XML)
		{
 
			_feed = data;
		}		
 
		public function first():TrackData
		{
			return getAt(_pointer = 0);
		}
 
		public function next():TrackData
		{
			if(_pointer >= count)
			{
				_pointer = 0;
				return null;
			}
 
			return getAt(_pointer++);
		}
 
		public function last():TrackData
		{
			return getAt(_pointer = length-1);
		}
 
		public function previous():TrackData
		{
			if(_pointer <= -1)
			{
				_pointer = length-1;
				return null;
			}
 
			return getAt(--_pointer);
		}		
 
		public function get name():String
		{
			return _feed.name.text();
		}
 
		public function get type():String
		{
			return _feed.@type.text();
		}
 
		public function getAt(index:Number):TrackData
		{
			if(index >= count || index < 0)
				return null;
			else
			{
				return TrackData.fromXML(_feed.trackList.track[index]);				
			}
		}
 
		// returns null if not found
		public function getById(id:Number):TrackData
		{
			var oldPointer:Number = _pointer;
			first();
 
			var data:TrackData;
 
			while(data = next())
			{
				if(data.id == id)
					break;
			}
 
			_pointer = oldPointer;
			return data;
		}
	}
}

As you can see, one of the class variables is _feed which is of type XML. Basically I keep the xml, and when needed I convert the data to TrackData for easy manipulation.

To use this feed, you would do like so :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
// provided we are in the onComplete handler of the WS call, and the "evt" is of type Event.
 
var xml:XML = XML(evt.target.data as String);
var feed:TrackFeed = new TrackFeed(xml);
 
trace("playlist is of type:"+feed.type);
trace("playlist is called:"+feed.name);
trace("playlist has "+feed.length+" elements);
 
var track:TrackData;
while(track = feed.next())
{
	trace("track ["+track.id+"] is "+track.title");
	trace("track url is :"+track.location);
	// and so on..
}

As you see, it really easy! It requires a bit of coding up front, but down the line it’s much clearer and simpler to use than Arrays or using data straight out of the XML.

The Custom Event

Now, from the function inside of the WSClient, which handles the Loader's Event.COMPLETE event, it would be nice to fire an event to carry this feed to all interested parties. Here is the implementation of the event:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
package events
{
	import flash.events.Event;
	import feeds.TrackFeed;
 
	public class PlaylistEvent extends Event	
	{
		public static const PLAYLIST_RECEIVED:String = "playlist_received";
 
		protected var _feed:TrackFeed;
		public function get feed():lib.musicnation.feeds.TrackFeed { return _feed; }
 
		public function PlaylistEvent(type:String, feed:TrackFeed)
		{
			super(type);
			_feed = feed;
		}
	}
}

To dispatch this event from the WSClient:

1
2
3
4
5
// provided we are in the onComplete handler of the WS call, and the "evt" is of type Event.
 
var xml:XML = XML(evt.target.data as String);
var feed:TrackFeed = new TrackFeed(xml);
dispatchEvent(new PlaylistEvent(PlaylistEvent.PLAYLIST_RECEIVED, feed));

That’s it for part 3 folks. In part 4 I will explain how to uniquely identify each request so that all multiple classes can use the WSClient singleton and ensure they do get THEIR results, and not the results asked for by another class.

Cheers!

2 thoughts on “Tutorial – Consuming REST web services in ActionScript 3 – Part 3

  1. Hi Martin,
    Your tutorials are owesome sources for Flash knowledge. I’ve a little request that I’m sure I’m not the first one to ask you for: could you provide a link of all your packages of classes for dowload? I’m not able to make your code working, maybe because I don’t create the packages structure the right way.
    Can you help?
    Thanks.

  2. Hi,

    THis is about a year late :) , but I thought I should put up a reply. First of all. The tutorials are wonderful.
    Now for the comment part :) . There are two classes which you could have used to clean up the code a bit. XMLEncoder en XMLDecoder. Two nice classes that can do the marshalling and unmarshalling for you instead of that handcoded fromXML() function.

    Kind regards

Leave a Reply

Your email address will not be published. Required fields are marked *

* Copy This Password *

* Type Or Paste Password Here *

55,121 Spam Comments Blocked so far by Spam Free Wordpress

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>