RESTful Flex/Flash client
Saturday, December 26, 2009
This week I had to program in AS3. I didn't code from scratch the application I worked on; however, I designed the architecture, so I was familiar with the source and felt confident to jump in to do updates. In the process, I was introduced to the default Flash development environment--CS4.
Coming from a world of Vim, Eclipse, NetBeans, and Visual Studio, I found the IDE lacking in functionality. True enough, I can compile code and export everything into SWFs files. But what about the little extras that make developing software fun? Where's the
vi plugin? Where's the build file? What about code completion?
A couple of Flash developers I know suggested I try Flash Builder, which is based on Eclipse. I downloaded it and coded my first application--a RESTful Flex client. My first application, however, is not a full AS3 app; it's a Flex app coded in a language that is a hybrid of XML and AS3.
The application, which I call TwitterFlex, looks as follows:
The running version is here
TwitterFlex: you click the button and it connects to Twitter's REST API to retrieve the latest 20 public updates.
Let me break down the code in 3 sections--XML stuff, AS3 code, and UI logic--because I think most Flex application will have the same code structure that my toy example has.
XML Stuff: the web service connectionConnecting to web services through HTTP is such a common requirement that the Flex API already includes code to do just that.
Creating an HTTP call that connects to Twitter is done with the following XML code:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute"
viewSourceURL="srcview/index.html">
(1) <mx:HTTPService
(2) id="RESTService"
(3) url="json.jsp"
(4) resultFormat="text"
(5) result="onLoadTweetsResult(event)"
(6) fault="onLoadTweetsFault(event)"
(7) showBusyCursor="true">
(8) </mx:HTTPService>
If you've seen XML files before, the first line should look familiar: with the
mx
directive we're telling whatever will parse this file that we are using Adobe's
http://www.adobe.com/2006/mxml
package.
Line (1) instantiates an
HTTPService
object; in line (2) I give the instance an id of
RESTService
. In line (3) I set the URL value of
json.jsp
(because of cross domain issues, I need to call a local pass through to talk to Twitter--this is a simple JSP and the code is at the end of this entry). Lines (5) and (6) point to the event handlers of the HTTP responses, with (5) handling success and (6) handling failure.
Note that I have a service instantiated, but I haven't connected to it yet. I leave the connection to the service when the user clicks a button (see the UI section below).
AS3 Code: the <mx:Script></mx:String> tagWith the ability to make web service calls, I need to program the event handlers of the HTTP responses and other functions that are needed for user interaction or logic that needs to be performed as part user requests. AS3 code is enclosed in the XML element
<mx:Script>
. The AS3 code in my application looks as follows:
<mx:Script>
<![CDATA[
import mx.controls.dataGridClasses.DataGridColumn;
import mx.messaging.AbstractConsumer;
import mx.rpc.events.ResultEvent;
import mx.rpc.events.FaultEvent;
import mx.collections.ArrayCollection;
import com.adobe.serialization.json.JSON;
[Bindable]
private var tweets:ArrayCollection;
private function loadTweets():void {
RESTService.send();
}
private function
onLoadTweetsResult(event:ResultEvent):void {
var rawJSON:String = String(event.result);
var arrayJSON:Array = JSON.decode(rawJSON) as Array;
tweets = new ArrayCollection(arrayJSON);
}
private function
onLoadTweetsFault(event:FaultEvent):void {
trace(event.fault.toString());
}
private function
getScreenName(tweet:Object,
column:DataGridColumn):String {
return tweet.user.screen_name;
}
private function
getName(tweet:Object,
column:DataGridColumn):String {
return tweet.user.name;
}
]]>
</mx:Script>
The import statements should be obvious. Next, however, is this
[Bindable]
statement just above the
tweets
variable. As per Adobe's documentation, this
metatag is an event listener hook that updates anything using the instance of the data with a message saying that the original copy changed. In short,
[Bindable]
makes
tweets
a global variable.
Next, comes the
loadTweets()
function, which tells the web service I defined earlier to run by executing the
send()
method of the
HTTPService
object.
Handling of the HTTP responses is delegated to the
onLoadTweetsResult()
and
onLoadTweetsFault()
methods. The former, is where Twitter's JSON object is parsed using a JSON library that is available for
download. Before you can use it, first download it and then add it to your Flex Builder's project library (I thought it was a default Adobe package, but it's not--let me save you some time here).
Finally, the last 2 methods,
getScreenName()
and
getName()
, return the value of the fields in the JSON object that I use in the UI components of the app, which I cover next.
The UIThe last portion of the code is the UI of the application. I won't cover the details of every XML tag available, because there are already many examples of this out there. What's more, I only use 5 UI elements: a
VBox
, a
Label
, a
DataGrid
, a
DataGridColumn
, and a
Button
. My UI, in code, looks as follows:
<mx:VBox
width="100%"
height="50%"
paddingBottom="60"
paddingLeft="60"
paddingRight="60"
paddingTop="60">
<mx:Label
text="RESTful Flex/Flash client (jose@josesandoval.com)"
fontSize="24"
fontWeight="bold" />
(1) <mx:DataGrid
dataProvider="{tweets}"
width="100%"
rowCount="12">
(2) <mx:columns>
(3)<mx:DataGridColumn
width="200"
headerText="Screen Name"
labelFunction="getScreenName" />
(4) <mx:DataGridColumn
width="200"
headerText="Name"
labelFunction="getName" />
(5) <mx:DataGridColumn
headerText="Tweet"
dataField="text" />
</mx:columns>
</mx:DataGrid>
<mx:Button
label="Get Tweets"
click="{RESTService.send()}" />
</mx:VBox>
</mx:Application>
The only lines I will cover in detail are numbered. Everything above and below them is obvious.
Line (1) instantiates a
DataGrid
object that is provided in the code space
mx
. The tag's element
dataProvider="{tweets}"
is passing the grid object the
tweets
global variable I instantiated in the AS3 code--you can see how the event dispatching makes sense in the context of the application: if the state of
tweets
changes, every component that is using it has to be notified.
Lines (3) and (4) define columns in the grid. The tag's element
labelFunction
is telling the instance of the particular column that it needs to run the function named in the element's value. For example,
getScreenName
calls the function coded earlier
getScreenName()
and
getName
calls
getName()
. If you look at the functions above, you see that I'm accessing the
user
element of the parsed JSON object.
What about the variable
column:DataGridColumn
in the method's signature? Well, that's another callback registration that it's telling the code that it will be used in an object of type
DataGridColumn
somewhere while executing.
And finally, line (5) doesn't use a function callback. Because the object
tweets
has been brought into the scope of the loop for the
DataGrid
object, I can access a tweet's element directly and therefore I use
dataField="text"
, where
text
is a member of the instance of the global
tweets
.
Final WordsThis XML and AS3 code hybrid is the next evolution of computer languages. Flex is a compilable meta-language, with AS3 scripting capabilities, that lets us take advantage of the ubiquity of the Flash player. Flex apps are Flash apps and will run on any browser that has a Flash player installed.
Even though I'm liking coding in Flash and this new meta-computer language, I wonder why we insist in recreating all the functionality of the web browser in Flash applications? Flash is cool and all, but we can do most of what it does in plain HTML and JavaScript code.
If you want to see the whole listing in one place, Flex gives you the option of attaching the source code to your deployed applications. The source for this app is here
TwitterFlex/srcview/index.html, which you can also access by right-clicking on the application and then selecting "View Source."
json.jspI mentioned earlier that I use a JSP file to serve as a proxy to talk Twitter from the hosting server. This is because you can't make direct calls from a Flash app to Twitter unless you are a registered user. I don't have a developer's API key, and for this example I still want to use the public stream. The JSP file looks as follows:
<%@ page contentType="application/json;
charset=UTF-8" %>
<%@ page import="java.io.BufferedReader,
java.io.IOException,
java.io.InputStreamReader,
java.net.MalformedURLException,
java.net.URL,
java.net.URLConnection" %>
<%
try {
URL twitter =
new URL(
"http://twitter.com/statuses/public_timeline.json");
URLConnection tc = twitter.openConnection();
BufferedReader in =
new BufferedReader(
new InputStreamReader(tc.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
out.println(line);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
in.close();
}
%>
I open a connection and make an HTTP GET call. I then stream the result back to the caller of the JSP, but I set the data stream to have a MIME type of
application/json
.
Comments:
Unfortunately, Flex/Flash HTTP implementation has a lot of limitations for REST you didn't touch on; it has success/failure handlers, but doesn't let you get access to the return code or body in the case of a 'failure', making lots of rest responses hard to parse.
It will only let you do GET and POST unless you use a proxy to access DELETE, TRACE, PUT. It limits your access to headers like authentication.
Ultimately, once you get past the simplest of cases with Flex and REST, you'll find yourself painted into a corner.
Geoffrey, you're right. I didn't look at the other 3 HTTP requests--PUT, POST, DELETE.
These are client limitations that actually translated into some server REST framework implementations. Some frameworks were too browser centric and got away with doing PUT and DELETE requests by faking them with hidden parameters inside of GET and POST requests.