Requirements
Prerequisite knowledge                                     
You should be familiar with ActionScript 3 and object-oriented terminology and principles. Some experience with frameworks is useful, but not required. Be sure you have first read Part 1: Context and mediators and Part 2: Models.
 
Required products
Flash Builder (Download trial)
 
Sample files
 
User level
Intermediate
 
This is the third part of my introductory series covering Robotlegs AS3. In the first part you learned what Robotlegs AS3 is and had a brief "HelloWorld" introduction to the Robotlegs Context and Mediator classes. We explored models in the second part. This article expands on those concepts and introduces services.
 
If you missed them, I recommend looking at them first:
 

 

What is a service?
 
A service is your web application's link to the I in RIA. Services provide your application with an API to access web services, data sources, and other applications. This could be accessing the Twitter API, your company/client's web server, the file system, or a host of other sources of data outside of your application.
 
A service makes remote calls and parses the results. After the results are parsed, the service will notify the rest of the application that data is ready and/or update a model with the results. We want the external data to live as short a life as possible within our applications. As soon as we get the results, they are converted to a format that is specific to the application domain. This might be XML, JSON, or typed objects from an AMF source. When we are working in a "controlled" environment—meaning the server-side mid-tier is within our control—sometimes we can avoid parsing altogether because the returned objects are already part of the application domain. In many situations we are accessing third-party servers and receiving "foreign" data. This is what we want to parse.
 
Let's make something!
 
So with this definition of a service behind us, let's look at how a service functions within a Robotlegs AS3 application. This example makes use of the basic Twitter Search API (see Figure 1).
 
fig01
Figure 1. Diagram of a service functioning within a Robotlegs AS3 application that makes use of the Twitter Search API.
 
As you can see in Figure 1, the service receives a request, asks the server for some data, parses the results, and notifies the rest of the application. The requests are generally made from mediators and commands. In this example, we are going to keep it simple and make requests to the service from a mediator. (In an upcoming article, I will talk about how commands are a great place to access services.)  
 
OK, so it's not the next TweetDeck but it is about as simple as it gets when accessing the Twitter API. The widget accesses the Twitter Search API and returns the current results for "robotlegs." You can download the archived Flash Builder project at the top of this article page.
 
We'll start out simple and add complexity later if we need to. Let's take a look at the pieces that go into to putting this together:
 
<?xml version="1.0"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:service="robotlegs.examples.service.*" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:view="robotlegs.examples.service.view.*" width="300" height="300"> <fx:Declarations> <service:ServiceExampleContext contextView="{this}"/> </fx:Declarations> <view:SearchResultsView/> </s:Application>
This is the main application view. You will notice that I have already made an instance of the ServiceExampleContext available in the Declarations tag. (If you haven't yet read Part 1 and Part 2 in this series, I recommend doing so now because they cover the basics of creating and initializing the Context.) In addition to the ServiceExampleContext, you see that the SearchResultView has also been added to the application. This example only has the one view. Take a look at the context:
 
ServiceExampleContext.as
 
public class ServiceExampleContext extends Context { override public function startup():void { mediatorMap.mapView(SearchResultsView, SearchResultsViewMediator); } }
In the SearchExampleContext we are mapping a mediator for the SearchResultsView. The SearchResultsView is a sub-class of the Spark List component:
 
SearchResultsView.mxml
 
<?xml version="1.0"?> <s:List xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" itemRenderer="robotlegs.examples.service.view.renderers.SearchResultItemRenderer" height="100%" width="300"> <s:layout><s:VerticalLayout gap="0" paddingBottom="10" paddingRight="10" paddingLeft="10" paddingTop="10"/></s:layout> </s:List>
The SearchResultsView needs a mediator to allow it to communicate with the rest of the application:
 
SearchResultsViewMediator.as
 
public class SearchResultsViewMediator extends Mediator { [Inject] public var view:SearchResultsView; override public function onRegister():void { //Make the magic! } }
Now that the SearchResultsView is properly mediated, we are ready to start looking at the service that will connect to the remote Twitter server and return some data for us to list. If you were looking closely at the SearchResultsView, you will see that we have also given it an ItemRenderer. We will be looking at that in just a bit. First, let's take a look at the basic service class that will connect to Twitter and fetch our data:
 
TwitterSearchService.as
 
public class TwitterSearchService extends Actor implements ISearchService { private var loader:URLLoader; public function TwitterSearchService() { loader = new URLLoader(); } private const TWITTER_SEARCH_ENDPOINT:String = &quot;http://search.twitter.com/search.json&quot;; public function getResults(forQuery:String = &quot;robotlegs&quot;):void { var urlRequest:URLRequest = new URLRequest(TWITTER_SEARCH_ENDPOINT); var params:URLVariables = new URLVariables(); params.q = HTMLStringTools.htmlEscape(forQuery); params.rpp = 100; urlRequest.data = params; addLoaderListeners(); loader.load(urlRequest); } private function handleError(event:SecurityErrorEvent):void { removeLoaderListeners(); } private function handleLoadComplete(event:Event):void { removeLoaderListeners(); } private function addLoaderListeners():void { loader.addEventListener(IOErrorEvent.IO_ERROR, handleError); loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, handleError); loader.addEventListener(Event.COMPLETE, handleLoadComplete); } private function removeLoaderListeners():void { loader.removeEventListener(IOErrorEvent.IO_ERROR, handleError); loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, handleError); loader.removeEventListener(Event.COMPLETE, handleLoadComplete); } }
TwitterSearchService has one public method, getResults(). It fetches results for a search string from the Twitter Search API. It is using the URLLoader to accomplish this and accessing the API directly. (There are a number of excellent Twitter API libraries for ActionScript available. I highly recommend investigating them if you are trying to write the next TweetDeck.) With the URLLoader, the TwitterSearchService makes a simple request to the appropriate endpoint. Listeners are added to the URLLoader and the fault/result handlers catch whatever is thrown back. Right now handleLoadComplete isn't actually doing anything except removing the listeners, which isn't very useful—but we will remedy that in just a moment.
 
Note that TwitterSearchService extends Actor and implements ISearchService. Actor simply provides convenient access to the EventDispatcher for this context. It is generally considered a best practice to implement an interface for a service. Take a look at ISearchService:
 
ISearchService.as
 
public interface ISearchService { function getResults(forQuery:String="robotlegs"):void; }
When you implement an interface for a service class, it makes it really easy to swap the service out. Why would you want to do that? When connecting to remote services, it is sometimes more effective during development to create mock data services with 0 latency. You could make a MockTwitterJsonService that returned the same static, hard-coded results every time. This approach is critical for unit testing, which will be covered in another article, as well as just general development. Many times you will find yourself developing in parallel with the team developing the service; the ability to create and replace mock services with live ones provides a lot of flexibility.
 
You will recall in my definition of a Robotlegs service previously that I mentioned parsing data as soon as it is returned from the remote service. In the case of the Twitter Search API endpoint we are using, we receive a JSON string as the result. JSON is a great format, but we don't want to start passing around raw JSON strings in our application. For one thing, it loses the benefit of strong typing. Besides, we want to pick and choose what information we are going to use in our application. In this case, we only need the user name and actual tweet text.
 
Tweet.as
 
 
public class Tweet { public var user:String; public var message:String; }
This class needs a sandwich! Not a lot going on here, but we now have something to convert the results to:
 
TwitterSearchResultParser.as
 
public class TwitterSearchResultParser implements ISearchResultParser { public function parseSearchResults(results:Object):Array { var decoder:JSONDecoder = new JSONDecoder(String(results)); var resultObjects:Array = decoder.getValue().results; var tweets:Array = []; for each(var result:Object in resultObjects) { var tweet:Tweet = new Tweet(); tweet.user = result.from_user; tweet.message = HTMLStringTools.htmlUnescape(result.text); tweets.push(tweet) } return tweets; } }
The results parser is a straightforward utility class. For the same reasons the service implements an interface, the results parser does as well. Its one method, parseSearchResults(), takes a results argument. The results from Twitter will be a string, but other results might be XML if we were to use local mock data or change which Twitter service we were accessing. The JSON string is encoded into objects, which are then converted to our Tweet value object from above. The resulting Array is returned as the result. Now that the parser is ready to go, we want to supply it to the service so that it can parse the results.
 
You have several options for giving the service access to the parser class. You could make its methods static, you could create a new instance of the parser class within the service, or you can leverage dependency injection to supply the parser to the service class. The problem with the first two options is that it makes it a lot harder to swap out the parser class later if we ever need to.
 
We are in the land of dependency injection now, so why not map the parser for injection? If we do this, it will be one single line in our context to change if we want to switch it out. There is no need to dig around in our classes commenting out lines and changing types, because both the parser and service conform to interfaces. If you'd like to see how easy it is to swap out a service, take a look at the Flickr Image Gallery in the Robotlegs Demo Bundle.
 
Let's look at mapping the service and parser interfaces in the context:
 
ServiceExampleContext.as
 
override public function startup():void { mediatorMap.mapView(SearchResultsView, SearchResultsViewMediator); injector.mapSingletonOf(ISearchService, TwitterSearchService); injector.mapSingletonOf(ISearchResultParser, TwitterSearchResultParser); }
This is the first time we have used the mapSingletonOf() method of the injector. It behaves much the same way as mapSingleton, with one critical difference. With mapSingletonOf() you are mapping the class you wish to inject via its interface or base class. Let's take a look at the TwitterSearchService and see how this is injected into our classes:
 
TwitterSearchService.as
 
private var _parser:ISearchResultParser; [Inject] public function set parser(value:ISearchResultParser):void { _parser = value; } private var loader:URLLoader; public function TwitterSearchService() { loader = new URLLoader(); }
With the above code added to the TwitterSearchService, you are now injecting the parser that was mapped into it. There are a couple of interesting things happening here. You will notice right away that the parser is of type ISearchResultParser. Since we used mapSingletonOf() to map our parser, it looks for a property of the base class or interface type that we mapped. It will actually inject the TwitterSearchResultParser but we will access it via its interface. This approach is referred to as polymorphism and is a power object-oriented concept.
 
We are also placing the [Inject] tag above the setter function and not the property itself. In addition to property injection, which we have used up until now, Robotlegs supports setter and constructor injection. In this case we are using setter injection to supply the value to the parser property's setter function. Constructor injection doesn't even require the [Inject] metadata to function. We will talk about construction injection later; for now just remember to add the setter function to the ISearchService interface:
 
ISearchService.as
 
public interface ISearchService { function getResults(forQuery:String="robotlegs"):void; function set parser(value:ISearchResultParser):void; }
Now that the parser is all sorted out, we are ready to actually use it in the service. The result handler is the likely spot to make use of the parser, so we will add some code there:
 
TwitterSearchService.as
 
private function handleLoadComplete(event:Event):void { var data:Object = loader.data; var results:Array = _parser.parseSearchResults(data); dispatch(new SearchResultEvent(SearchResultEvent.RECEIVED, results)) removeLoaderListeners(); }
There we go: The data is extracted from the URLLoader, it is sent to the parser to be converted into an Array of Tweet objects, an event is dispatched notifying the rest of the app that we now have results, and the event listeners are removed. The SearchResultEvent is a simple custom event that has an array as its payload:
 
SearchResultEvent.as
 
public class SearchResultEvent extends Event { public static const RECEIVED:String = "searchResultsReceived"; private var _results:Array; public function get results():Array { return _results; } public function SearchResultEvent(type:String, results:Array = null, bubbles:Boolean = false, cancelable:Boolean = false) { super(type, bubbles, cancelable); _results = results; } override public function clone():Event { return new SearchResultEvent(type, results, bubbles, cancelable) } }
At this point in development we have a service that should work, a mediated view component, and an event being dispatched to deaf ears. It would be more accurate to say it's an event that is never dispatched at all. We haven't wired the service anywhere to call getResult(). We will take care of that in the SearchResultViewMediator:
 
SearchResultsViewMediator.as
 
public class SearchResultsViewMediator extends Mediator { [Inject] public var view:SearchResultsView; [Inject] public var service:ISearchService; override public function onRegister():void { service.getResults(); addContextListener(SearchResultEvent.RECEIVED, handleResultsReceived) } private function handleResultsReceived(event:SearchResultEvent):void { view.dataProvider = new ArrayCollection(event.results); } }
Now we're talking! Again the service is injected via its interface and in the onRegister() we make a call to getResults(). The magical hamsters at Twitter HQ generate some content that our service parses and notifies the rest of the application with an event carrying the data. The mediator is listening for that event, and in handleResultsReceived it supplies the results to the view's dataProvider as an ArrayCollection. All we need to add is the ItemRenderer and we will have a fully semifunctional Twitter client:
 
SearchResultItemRenderer.mxml
 
<?xml version="1.0"?> <s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" width="260" height="85"> <fx:Script><![CDATA[ import robotlegs.examples.service.model.Tweet; override public function set data(value:Object):void { super.data = value; var searchResult:Tweet = value as Tweet; if(searchResult) { message.text = searchResult.message; username.text = searchResult.user; } else { message.text = ""; username.text = ""; } } ]]></fx:Script> <s:layout> <s:VerticalLayout paddingBottom="5" paddingRight="5" paddingLeft="5" paddingTop="5"/> </s:layout> <s:Label id="username" width="100%" maxDisplayedLines="1" fontWeight="bold"/> <s:Label id="message" width="100%" maxDisplayedLines="4" /> </s:ItemRenderer>
There you have it—a list of tweets that mention Robotlegs. Glorious indeed! I would encourage you to play with this application a bit. Add a control bar to change the search terms. Add the ability to log in, retrieve your timeline, and maybe post a tweet. If you don't want to piss off all your nerd friends, start a new Twitter account to play with while you develop; I hear they have plenty available.
 
Now that we have services, we've almost covered the full scope of tools for utilizing the Robotlegs MVC+S implementation. All that we have left to cover is commands, which will let us decouple the code and provide reusable "actions" that are triggered by events.
 
If you can't wait, there are articles on my blog (and lots of others across the internets) dealing with various Robotlegs topics including a 25-minute screencast of mine. John Lindquist has a Hello World screencast on his blog. Additionally there is a best-practices document that has proven helpful for many. You can always hit the Robotlegs Knowledge Base for help and support. It has an active group of community volunteers, including myself, that diligently answer questions regarding all things Robotlegs. For an in-depth discussion of these topics and more, see ActionScript Developer's Guide to Robotlegs.
 
Permissions beyond the scope of this license, pertaining to the examples of code included within this work are available at Adobe.