In order to build the Application Starter Kit I mentioned before, I'll start by building a simple Blog Reader application. Yes, I know blog reader apps have been done before. But it's a simple application that showcases remote services, XML reading and parsing, and other features that will be useful for the Starter kit.
This Blog Reader will display a list of articles from a RSS 2.0 feed. When you select the title of an article the app will display the text of that article.
If I were doing a *real* blog reader application I would use use the XML Syndication Library provided by Adobe on the Labs site. However that would make things too easy, since the library handles all the RSS parsing for you. So I'll stick to a no-frills approach to RSS reading for this example.
The app will connect to a RSS feed by using a Flex 2 HTTPService object. A real application might want to connect to more than one remote service, and I want to make sure that the same pattern is used for each one. In addition, it would be nice to define one standard way of handling faults when remote service calls fail. This calls for a separate class or component to manage all of the remote services for the app.
I've called this class AppServices. Its job is to define all of the remote services used in the application and to provide a common fault handler in case problems occur.
Because it's so easy to use Flex 2 data binding to fill in request parameters for the MXML HTTPService object, I'll also define the AppServices class in MXML.
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"
height="0"
visible="false">
<mx:Script>
<![CDATA[
import mx.rpc.events.FaultEvent;
/**
* The URL of the current RSS 2.0 feed.
*/
[Bindable]
public var rssUrl:String =
"http://rantworld.blogs.com/flashlit/rss.xml";
/**
* Handles reporting for any kind of service fault.
*/
public function onServiceFault(evt:FaultEvent):void
{
var err:Error =
new Error(evt.fault.faultCode +
" , " + evt.fault.faultString);
parentApplication.showError(err);
}
]]>
</mx:Script>
<!-- ======================================== -->
<!-- Remote Services -->
<!-- ======================================== -->
<!-- RSS 2 Feed -->
<mx:HTTPService id="rss2Feed"
url="{this.rssUrl}"
fault="onServiceFault(event)" />
</mx:Canvas>
Even though it's based on the Canvas component the AppServices component will be hidden, so its visibility="false" and its height="0".
Right now the AppServices class only defines one service, an RSS 2.0 feed for this very blog:
[Bindable]
public var rssUrl:String = "http://rantworld.blogs.com/flashlit/rss.xml";
This variable is bindable so we can change it when we want to redirect the service to a different RSS 2.0 feed. This is similar to the urlPrefix
shown in my previous post about Redirecting services using data binding.
The onServiceFault()
method provides a common way of handling faults from an HTTPService or a WebService. The application has a common method for displaying errors, the showError()
method, which takes an instance of the Error class. The onServiceFault()
method generates an Error instance with a message describing the fault that occurred.
Finally the RSS 2.0 feed itself is defined as a HTTPService object. The url
attribute is bound to the rssUrl
variable and the fault
event handler is set to the onServiceFault()
method.
Now from the main application you can refer to an HTTPService defined in the AppServices component like this:
this.services.serviceId
Or for web services:
this.services.serviceId.operationName
You can also call a service from any other class in your application like this:
parentApplication.services.serviceId
The advantage to this approach is that you don't have service definitions sprinkled throughout your application. They are located in one spot and much simpler to maintain as a result.
Notice that the AppServices component never calls the send()
method the service. Nor does it specify which methods will handle the result
event. Sending and listening for results will be handled elsewhere in the application or in other components that want to call services and display their results.
Here is the code for the SimpleBlogReader application itself (SimpleBlogReader.mxml):
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:app="*"
layout="vertical"
horizontalAlign="left"
paddingTop="5"
paddingLeft="5"
paddingBottom="5"
paddingRight="5"
creationComplete="initApp()">
<app:AppServices id="services" />
<mx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.collections.ArrayCollection;
import mx.events.CloseEvent;
import mx.rpc.events.ResultEvent;
public function initApp():void
{
// show the link HTML
this.linkLbl.htmlText = "From <a href='http://www.flashlit.com' target='_blank'><u>FlashLit.</u></a>";
// set up a handler to receive RSS feed results
this.services.rss2Feed.addEventListener(ResultEvent.RESULT, onBlogDataReceived);
}
/**
* Called when the RSS feed data has been received.
*/
public function onBlogDataReceived(evt:ResultEvent):void
{
try
{
// first make sure we received an rss object
if (evt.result.hasOwnProperty("rss"))
{
/*
* Because the resultFormat for the service is "object" the list
* of articles (items) has been returned as an ArrayCollection.
* The statement below is kind of ugly and will only work for RSS 2.0 feeds.
* It will throw an error if the channel or item properties don't exist.
*/
var itemList:ArrayCollection = evt.result.rss.channel.item as ArrayCollection;
/*
* The list control's labelField is set to "title" so the item title will
* show in the list.
*/
this.articleList.dataProvider = itemList;
if (itemList.length > 0)
{
// selects and shows the first article
this.articleList.selectedIndex = 0;
this.showArticle(itemList.getItemAt(0));
}
}
else
{
showError(new Error("The data received was not in RSS 2.0 format."));
}
}
catch (e:Error)
{
this.showError(e);
}
}
/**
* Called when the getRssBtn button is clicked.
*/
public function onGetRss(evt:Event):void
{
if (this.urlTxt.text != "")
{
// sets the url in the AppServices component
this.services.rssUrl = this.urlTxt.text;
// invokes the service to retrieve the RSS data
this.services.rss2Feed.send();
}
}
/**
* Called when an article title is selected from the list.
*/
public function onArticleSelected(evt:Event):void
{
if(evt.target.selectedItem != null)
{
showArticle(evt.target.selectedItem);
}
}
/**
* Displays the article content. Currently this just fills a TextArea
* with the article HTML.
*/
public function showArticle(articleObj:Object):void
{
if (articleObj != null)
{
this.articleTxt.text = "";
this.articleTxt.htmlText = articleObj.encoded;
}
}
/**
* A common method for displaying error messages.
* Currently it just shows the message in an Alert control
* Tracing or logging of errors can be added here.
* Based on the severity of the error other actions could be taken as well.
*/
public function showError(e:Error):void
{
var a:Alert = Alert.show(e.message, "An Error Occurred", 4.0, this, onAlertClosed);
}
/**
* Called when the error alert is closed. In the future, based on whether the error was
* fatal or just a warning, this method could end up quitting the application.
*/
public function onAlertClosed(evt:CloseEvent):void
{
trace("The error alert was closed.");
}
]]>
</mx:Script>
<mx:Label id="titleLbl" text="A Simple RSS 2.0 Blog Reader" fontSize="20" styleName="appTitle" />
<mx:Text id="linkLbl" text="" fontSize="12"/>
<mx:Label id="urlLbl" text="Retrieve RSS 2.0 from URL:" width="160" />
<mx:HBox width="100%">
<mx:TextInput id="urlTxt" text="http://rantworld.blogs.com/flashlit/rss.xml" width="100%" enter="onGetRss(event)" />
<mx:Button id="getRssBtn" label="Go" width="80" click="onGetRss(event)" />
</mx:HBox>
<mx:Label id="articleListLbl" text="Articles:" width="160" />
<mx:List id="articleList" width="100%" height="140" labelField="title" change="onArticleSelected(event)" />
<mx:TextArea id="articleTxt" width="100%" height="100%" />
</mx:Application>
First there's an instance of the AppServices class which is given the id "services":
<app:AppServices id="services" />
Then in the initApp()
method, which is called after the application has loaded, the application adds a listener to the rss2Feed
service.
this.services.rss2Feed.addEventListener(ResultEvent.RESULT, onBlogDataReceived);
The service is called when the user click on the "Go" button, which triggers the onGetRss()
method. First the rssUrl
property is set, and then the send()
method is called:
// sets the url in the AppServices component
this.services.rssUrl = this.urlTxt.text;
// invokes the service to retrieve the RSS data
this.services.rss2Feed.send();
When the RSS data is received from the service the result event listener kicks in and triggers the onBlogDataReceived()
method. The resultFormat for the rss2Feed
service wasn't explicitly set, so it defaults to "object" meaning the data is returned as an anonymous ActionScript object rather than XML. There are pros and cons to this format and in many cases receiving the data as XML is preferable -- but that can be a topic for another day. In this case getting the data as an ActionScript object makes the next step quite easy. The list of items is extracted from the result as an ArrayCollection and used directly as the dataProvider
property for the List control:
var itemList:ArrayCollection = evt.result.rss.channel.item as ArrayCollection;
/*
* The list control's labelField is set to "title" so the item title will
* show in the list.
*/
this.articleList.dataProvider = itemList;
The last item of note is the showError()
method. It only shows a simple Alert right now, but it can be enhanced in the future to handle tracing, logging, or other kinds of error handling logic.
The Simple Blog Reader application is embedded below, and you can also download the source files here.
Note the painfully awkward HTML formatting in the TextArea. I'll do something about that in a future installment.
As always I'm open to any suggestions for improving the code.
Comments