Share Media on Twitter Using Flex, Part I: The Basics

Everyone’s Twittering! In this series of articles, we’ll investigate how to build an attractive, functional Flash front end for Twitter, using the new beta version of Adobe’s Flash Builder 4, Flash Catalyst Beta, and PHP. We’ll start with basic functionality, and later parts will deal with sharing images with your friends.

To follow along with this series, you’ll need:

In this article we’ll assume that you’ve already spent some time in Flash Catalyst, and become familiar with how it works – the techniques we’ll be using in this article are covered in Flash Catalyst: Mockup to Masterpiece, Part I and Part II.

Note: A huge thanks to SitePoint’s Raena Jackson Armitage for her help with writing the PHP parts of this tutorial.

Converting the Interface

We’ve asked a designer to create the application’s interface for us, which we received as a PSD. Here’s how it turned out:

Our mockupOur mockup (View larger image in a new window.)

You’ll find this file in the code archive, in the folder called PSD.

The quickest way to convert a design like this into a Flex project is by using Flash Catalyst. Catalyst is Adobe’s new interaction design tool that’s been recently released as a public beta. The interface for our application was created for us by our designer in Photoshop, imported into Flash Catalyst, and converted into a Flex project. Flash Catalyst preserves most of the elements in the original Photoshop file so that it can be reopened in Photoshop for later adjustment.

While Catalyst is stable enough to be used, it’s important to remember that it is beta software and its feature set is incomplete; that’s why it’s best to do most of the design work in Illustrator or Photoshop, where you have the most appropriate tools.

The interface for our Twitter application is simple: its main components are a scrolling list of Twitter posts and a text input field within a form to submit user posts.

We’ll convert the scrollbar design into a vertical scrollbar component using the Convert Artwork to Component tool in Catalyst. I found that Catalyst had a little trouble with the arrows, so I had to tweak its design so that the up and down buttons sat outside of the scrollbar’s track. That’s easy enough to do; simply grab the arrows and move them out.

Next, we’ll address the list of tweets – we’ll delete all but the first of the tweets, since Catalyst only needs one to determine what every row should look like. Then, select the first row in the data list design plus the vertical scrollbar, and convert these into a Data List component. At the bottom of the design, we’ll turn the form elements into Text Input and Button components.

There’s a missing element in our design, however: we need to create a state for the login screen. To do this, we’ll duplicate the whole design for the UI and remove the list of tweets and posting form, and replace these with a login form. It’s easy enough to draw these using Catalyst’s tools. The two states should have names so that it’s easy to tell them apart later in code; I’ve called mine twitterLogin and twitterDisplay.

We can define how Flash displays the change between the two states by adding animation to the timeline. Let’s add a cross-fade effect after the user has submitted the login form. We can do this in the Timeline area, located below the design canvas.

That’s all we need to do in Catalyst. Save your Catalyst work as a Flex project, and make yourself a cup of tea. Our version of the freshly converted file is in your code archive, named cheepcheep_flashbuilder.fxp.

The next part is to prepare our PHP proxy.

Building a PHP Proxy

Our Twitter tool needs to be able to communicate with Twitter in order to send and receive tweets. However, the Flash Player’s security model prevents a Flash file from accessing data from another domain; that is, a Flash movie hosted on a page at www.example.com is unable to retrieve data from www.example.net – unless permission is explicitly granted by that domain with a file called a cross-domain policy file. You can read more about the cross-domain policy file at Adobe here.

Looking at Twitter’s cross-domain policy file, we can see that it will only accept calls from Flash movies hosted at twitter.com:

 
<?xml version="1.0" encoding="UTF-8"?>
<cross-domain-policy
 xsi:noNamespaceSchemaLocation="http://www.adobe.com/xml/schemas/PolicyFile.xsd">
 <allow-access-from domain="twitter.com"/>
 <allow-access-from domain="api.twitter.com"/>
 <allow-access-from domain="search.twitter.com"/>
 <allow-access-from domain="static.twitter.com"/>
 <site-control permitted-cross-domain-policies="master-only"/>
 <allow-http-request-headers-from
    domain="*.twitter.com" headers="*" secure="true"/>
</cross-domain-policy>

That means any Flash movies hosted elsewhere – such as our site – are unable to retrieve data.

Instead, we’ll create a PHP-based proxy to run on our own server. We’ll set it up to accept POST requests from the Flash movie, send them on to Twitter, and then pass Twitter’s response back to the Flash movie.

We could write an entire Twitter API suite, but why reinvent the wheel? Twitter’s API Wiki helpfully lists a number of pre-built libraries we can use. In this example, we’ll include the twitterlibphp library to form the basis of our proxy. The class inside it, Twitter, contains functions for every conceivable Twitter action, such as updating your location or following new people – for a full list of Twitter’s API methods, see the API documentation on Twitter’s API wiki. Our app is fairly simple for now, but we’d like to make sure we have the ability to grow in the future, so twitterlibphp is a good choice.

First, then, we need to include the twitterlibphp library within our script:

require("twitter.lib.php");

At the moment, the Flash movie only sends updates and retrieves the user’s friends’ timeline, so we can expect to receive a username, a password, and possibly a message. There’s a fourth variable that we’d like to receive from the Flash app, which will define the type of action we want to perform.

Since we’ll be expecting the Flash movie to use POST to send us the data we need, we may wind up receiving some unwanted backslashes (); this can occur when magic_quotes_gpc is configured in your copy of PHP. Magic quotes are now deprecated in PHP, but there are still plenty of web hosts out there with them switched on, so it’s always worth checking for this configuration when you accept POST submissions. Here’s a simple function to strip slashes in case magic quotes are enabled:

function tidyslashes($text) {  
 if (get_magic_quotes_gpc()) {  
   $cleantext = stripslashes($text);  
 }  
 else {  
   $cleantext = $text;  
 }  
 return $cleantext;  
}

We’ll use that function to clean up the tweet-related variables we expect to receive, which are declared as global variables:

$username = tidyslashes($_POST['username']);  
$password = tidyslashes($_POST['password']);  
$message = tidyslashes($_POST['message']);

We’ll also need three functions – one to update, one to retrieve tweets from the user’s friends, and one to view the public timeline. Each function is fairly similar; we’ll just take a look at one for now. Here’s the update function:

function update() {  
 global $username, $password, $message;  
 $twitter = new Twitter($username, $password);  
 $update = $twitter->updateStatus($message);  
 print_r($update);  
}

What’s happening here? First, we grab the username, password, and message variables. Next, we specify a new instance of the Twitter class from twitterlibphp, passing the username and password along. Third, we specify the variable update as the result of the Twitter class’ updateStatus function, and simply print the result, which will be in XML format.

Additionally, we’ll have received a variable, named type, from the Flash movie to tell us which action we want to perform. We’ll use a switch statement to execute the appropriate function. If type was not specified, or says anything other than update, friends, or public, the script will die with a message:

switch($_POST['type']) {  
 case 'update':  
    update();  
    break;  
 case 'public':  
    getpublictimeline();  
    break;  
 case 'friends':  
    getfriends();  
    break;  
 default:  
    die("died: not called properly");  
    break;  
}

You can view the script in full here, and there’s a copy in the code archive too.

Copy the completed script, along with twitter.lib.php to a web server, and note the URL for later – we’ll need to tell our Flash application where this lives. If you downloaded our version of the script, you’ll find a very basic form you can use to test the updates function of your proxy. If all went well, you should be able to post to a Twitter account using that form!

If your web server is not running locally, your next step is to include a cross-domain policy file on your web server, so that it’s easier to run the app later. Here’s a permissive example policy file that will permit access from anywhere:

<?xml version="1.0"?>  
<!DOCTYPE cross-domain-policy  
SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">  
<cross-domain-policy>  
 <site-control permitted-cross-domain-policies="all" />  
 <allow-access-from domain="*" />  
 <allow-http-request-headers-from domain="*" headers="*" secure="false"/>  
</cross-domain-policy>

You’ll need to upload that file to the root of your domain, and name it crossdomain.xml.

Now that we’ve prepared this proxy, we can begin to add functionality to our Flash project and start testing.

Coding the Application in Flash Builder

From the Flex project in Flash Builder, open Main.mxml from the default package folder in the project’s src directory. This is the root of the application, indicated by the green arrow and blue dot symbols; these mean that the root element of this file contains an Application tag, and that the compiler will treat this as the root file of the project.

Towards the bottom of this code you’ll see a List tag – this is the data list that you created in Flash Catalyst. For now, it contains dummy data inserted by Catalyst inside an ArrayCollection, which we’ll need to tweak. Look at each of these rows of data and you’ll see that there are values for each of the images used. Only one of these will be dynamic later on, which is the profile picture for Twitter users. The others are part of the interface, and will be static.

Open the components folder, and you’ll see that one of the components that it contains is called RepeatedItem1.mxml – in that file you’ll find a number of BitmapImage tags towards the bottom of the code. Switching between this and Main.mxml, copy and paste the image location values for image2, image3, image4, and image5 from the first row of the repeated list data respectively, overwriting the source attribute for the first four BitmapImage tags.

Replacing BitmapImagesReplacing BitmapImages (View larger image in a new window.)

Next, we need to modify the custom component containing the login form elements. Locate and open CustomComponent1.mxml from the components directory, and look for the richtext1 RichText component. This is the login error message that we’ll display, so here, we need to set the visible property of this component to false – it can be switched on when there’s an unsuccessful login attempt.

We’d also like to ensure that the password field behaves as expected, displaying asterisks instead of characters, so we’ll add an attribute to that field. Look for the textinput0 TextInput component; we’ll add a displayAsPassword attribute with a value of true. The Script block at the top of this component’s code was placed there by Catalyst to switch to the list state when the user logs in. Right now this is called as soon as the user clicks on the Log In button. However, we only want to show that state if the user’s login was correct, so we’ll need to alter the code here.

The function in the Script block uses a call to the path mx.core.FlexGlobals.topLevelApplication to refer to Main.mxml. The remainder of that code changes the visual state of the application to the one called twitterDisplay – that’s the state with our list of tweets. Instead of switching states here, we’re going to do that in another part of the application. We’ll use the path to set the value of variables in that file to the user login and password details when they’re entered into the form; then call a function to fetch the user’s friends’ timeline data from Twitter via our PHP proxy. We’ll do that by adding the following lines to the existing function:

mx.core.FlexGlobals.topLevelApplication.userLogin=textinput4.text;  
mx.core.FlexGlobals.topLevelApplication.userPassword=textinput0.text;  
mx.core.FlexGlobals.topLevelApplication.fetchFriendsTimeline();

The first two take the text values of the two input fields in the form and set the values to variables in Main.mxml, while the third line calls a function we’ll write to call the PHP proxy. Switch back to Main.mxml, where we’ll add a Script block to the top of the code:

<fx:Script>  
 <![CDATA[  
 ]]>  
</fx:Script>

The CDATA comments are necessary so that the compiler knows that the contents of the script block should not be evaluated as XML. Inside those tags we'll create an ArrayCollection to store the data returned from the PHP proxy and populate the data list. We've added the Bindable compiler directive so that these three variables will broadcast any changes to their values during the operation of the application. We'll also add another variable, twitterCallType, to use later as a switch for processing Twitter results within a function:

import mx.collections.ArrayCollection;   
private var twitterCallType:String;  
[Bindable] private var twitterAC:ArrayCollection;  
[Bindable] public var userLogin:String;  
[Bindable] public var userPassword:String;

We'll add a dataProvider attribute to the List tag towards the bottom of the code and set it to the ArrayCollection, binding it to that variable by surrounding it with curly braces. By now we've removed the ArrayCollection from the list that was created by Catalyst. In this example, we used an XML-style self-closing element - that's why there's no closing List tag:

<s:List x="99" y="124" skinClass="components.DataList1"   
 d:userLabel="Data List"  
 visible.twitterLogin="false"  
 id="list1" dataProvider="{twitterAC}" />

Next up, we'll add our call to the PHP proxy.

Connecting to Data Services

We'll next add a HTTPService tag to call the PHP proxy. First, we create an instance of the tag, define its properties, then call it later from either an event or function as we'll be doing for our application.

Flex 4 requires service tags to be nested within the new Declarations tag. Our PHP proxy was built to receive calls via the POST method, so we'll add that attribute to the HTTPService tag along with the URL for the proxy. We name the instance with the id attribute. When you use the result event of the tag to call a function, Flash Builder will prompt with an offer to build a basic function when you add result to the tag. Add the showBusyCursor attribute to the tag, which will tell the user that we're waiting for a response from the server:

<fx:Declarations>    
<mx:HTTPService id="twitterService" method="POST" url="http://localhost/twitterapp/twittery.php" result="httpservice1_resultHandler(event)" showBusyCursor="true"/>    
</fx:Declarations>

That fetchFriendsTimeline function called earlier by the login form needs to be built. This function will be defined as public to make it available to other components in the application. Within the function, call the send method of the HTTPService instance, using an object to pass the parameters required by the PHP proxy. Using curly braces, we'll make a comma-separated list of name/value pairs for each of the parameters.

Remember the variables we created in our PHP script? We'll use those now. In this case, we need to send username and password, which are the details we received from the login form, as well as a type of call: at the moment, this could contain a value of update or friends:

public function fetchFriendsTimeline():void {    
 twitterService.send({username:userLogin,password:userPassword,type:"friends"});    
 twitterCallType = "friends";    
}

Results from the Twitter API call are handled by a function created by Flash Builder when we added the result event to the HTTPService tag. This is currently empty, so we need to add logic to that now. Notice that the httpservice1_resulthandler function has an event parameter typed as ResultEvent: an import statement for this class was created when the function was added by Flash Builder. We've written conditional logic to test the value of twitterCallType; we'll process the results of the HTTPService call if its value is friends.

Debugging the Data Results

At this stage, the structure of the result to be returned by the PHP proxy call is an unknown, so we're going to check that out before writing the rest of this function. Here, we'll set a breakpoint in the code and debug the application. We'll insert our breakpoint in httpservice1_resulthandler, at the line where we initialize twitterAC:

protected function httpservice1_resultHandler(event:ResultEvent):void {    
 if (twitterCallType == "friends") {    
   twitterAC = new ArrayCollection();    
 }    
}

Set a breakpoint by double-clicking on the line number next to where we've initialized twitterAC and run the application by clicking on the Debug button - it's the button on the toolbar with an icon resembling a bug.

Now run the code: if all went well, we should receive a response from Twitter via the PHP proxy, the app will pause at the breakpoint, and Flash Builder will prompt you to switch back to Flash Builder and enter the debug perspective. This is a different set of panels or views that include the Variables view, located as a tab at the top right of Flash Builder. Double-click on the Variables tab to expand it and you'll see that event is listed there. Expand that, then expand the default result property: in here you'll see there's a statuses property that Twitter has returned.

A portion of the debugger perspectiveFigure 3: A portion of the debugger perspective (View larger image in a new window.)

This was originally an XML response, and the HTTPService tag has automatically interpreted into a native object for you. One of the statuses properties is an ArrayCollection called status, with each item inside representing a Twitter post. Each post includes information about the post itself, with a nested user property containing details about the Twitter user who generated the post.

We'll be using some of these variables to populate the items in our list of tweets, which has spots for the user's screen name, real name, the text of the tweet, and the tweet's date and time. Normally you'll need to make note of their positions within the structure of the data. We're looking for variables called name, screenName, profile_image_url, text, and created_at. If you see them, great - it means you've successfully retrieved your friends' timeline from Twitter!

Double-click on the Variables tab again to restore Flash Builder to the way that it was. Exit the debug session by clicking on the terminate button, which is a red square (like your DVD remote's Stop button) at the top of Flash Builder. It's important to exit Flash Builder debugging sessions whenever you're finished, otherwise you'll find your browser locks up.

We also need to debug what will happen if the user enters an incorrect username and password. Start debugging again, and this time, use login details that you know to be wrong. You should see via the Values panel in the debug perspective that the data returned is quite different - unlike the collection of status updates received on a successful login, we can now see an event.result.hash.error property instead.

We can now exit the debugger. Let's also remove the breakpoint from Flash Builder by double-clicking on it in the line numbers.

Populating the Application with the Results

Armed with the knowledge of what Twitter's response looks like, we're now going to use this data to populate the twitterAC ArrayCollection that we created earlier. ArrayCollections are a kind of wrapper class that expose an array as a collection of objects that have additional methods and properties. A neat feature of ArrayCollections is that they can be used as data providers for components, and are able to broadcast changes to their contents.

First, we'll need to add some logic to the httpservice1_resultHandler function to handle an incorrect Twitter login. We're going to check if there is an event.result.hash property; if so, we'll display an error, otherwise, we'll process the data and display the list. We do this by surrounding all the code in the function with the test if (!event.result.hash), with an else condition to show the error.

Within the first part of the if statement, we nest another test to check the current state. If its value is twitterLogin, we'll swap the state to twitterDisplay.

Next, we loop over the status ArrayCollection nested in the result. With each iteration we create an object from the values we want to use in the data list repeated item, pointing to the values using a mixture of array and dot notation. Each object is then pushed into the twitterAC ArrayCollection using the addItem method.

Let's see the full function:

protected function httpservice1_resultHandler(event:ResultEvent):void    
 {    
   if ( !event.result.hash ) {    
     if (currentState == "twitterLogin") {    
       currentState = "twitterDisplay";    
         }    
     if (twitterCallType == "friends") {    
       twitterAC = new ArrayCollection();    
       var resultsLen:Number = event.result.statuses.status.length;    
       for (var i:Number=0;i<resultsLen;i++) {    
twitterAC.addItem({userName:event.result.statuses.status[i]. user.name,    
screenName:event.result.statuses.status[i].user.screen_name,    
profileImage:event.result.statuses.status[i].user.profile_image_url,    
tweetText:event.result.statuses.status[i].text,    
tweetDate:dateFormatter.format(new Date(event.result.statuses.status[i].created_at))});    
         }    
     } else {    
         fetchFriendsTimeline();    
         }    
   } else {    
     customcomponent11.richtext1.visible = true;    
     }    
 }

We've used a Flex tag to format the created_at variable that we've extracted from the XML file received from Twitter via the PHP proxy. The formatString attribute of the DateFormatter tag allows us to create a date string similar to the one our designer provided in the Photoshop file. Look at the last value in the data object that we're creating in the httpservice1_resultHandler function and you'll see the date formatter in action:

<mx:DateFormatter id="dateFormatter" formatString="MMM DD YYYY H:NN"/>	

That date string is case sensitive, so the date and time values must be capital letters.

When you add the DateFormatter, Flash Builder will add an additional namespace for you into the Application tag for the MX tag collection.

Displaying the Results

The twitterAC ArrayCollection is bound to the dataProvider property of the List component. When the ArrayCollection is repopulated, it will broadcast that its values have changed and cause the list to refresh itself. It will then loop over the dataProvider, passing the value from each iteration to the RepeatedItem component. That component will receive the value each time in a default object called data. We need to assign the properties of data to their respective components for display.

Open the RepeatedItem component again and take a look at the various RichText components used to display text; default values were assigned to the text properties of these components using curly braces for binding. We're going to replace those values with the properties of the object that we created each time we looped over the result data in the httpservice1_resultHandler function.

Going through those components, replace the text value {data.text2} with {data.tweetDate}, {data.text3} with {data.userName} ({data.screenName}), and {data.text4} with {data.tweetText}.

There's a small gotcha with the code Flash Catalyst created for us: the BitmapImage tags Catalyst added to display images in the repeated item are unable to accept a data binding as a value for its source. The user icon needs to display that dynamic information, so we'll need to change that to an Image tag. Back in RepeatedItem1.mxml, look for the set of images and replace the user icon BitmapImage with an Image, like so:

<mx:Image source="{data.profileImage}"     
 x="1" y="1" width="51" height="51"/>    
Updating the User Status

Posting a status to Twitter is quite straightforward. The username and password that we received from the user at the beginning are needed again to call the update function of the PHP proxy, and this time we'll also need to send the text value from the tweet posting area as the text for our message. To do this, we can reuse the HTTPService tag that we used to fetch our friends' timeline, calling update instead of friends.

Look at the httpservice1_resultHandler function again and you'll see that this will be caught by an else, which will call fetchFriendsTimeline again. The updated friends list will also show our new status:

public function postStatus():void {     
 twitterService.send({username:userLogin,password:userPassword,message:textinput1.text,type:"update"});    
 twitterCallType = "update";    
}
Conclusion

So far we've created a simple app that can read and post tweets. Some simple Twitter functionality, like replies and direct messaging are missing for now; we'll cover these later in the series, and then add some nifty image uploading and image manipulation features.

The fact that the application doesn't store username and password locally or securely is probably a good topic for future discussion; we've chosen to skip that for now for brevity's sake, and you may treat this as an exercise for the reader. Your PHP proxy should also, ideally, contain some logic to determine whether the request has actually been received from the Flash movie, and fail if the request comes from elsewhere.

For now, save your work and relax - we'll see you again in Part 2!

Note: Remember to grab the code archive for this article, so you can experiment at home some more.

Feeling confident? Test your knowledge of what you've learned here today in our quiz, sponsored by Adobe.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

No Reader comments

Comments on this post are closed.