Share Media on Twitter Using Flex, Part II: Images

In the first part of this series on sharing media on Twitter with Flex, we discussed how to create the interface for a Flex application with the beta of Flash Catalyst. Then we used the beta of Flash Builder 4 to create a Twitter application for the browser that used PHP to proxy calls to the Twitter API. In this article we’re going to enhance that Flex application by adding the ability to upload photographs to the popular Flickr image hosting service, and then integrate a shortened link to the photo into a post to Twitter.

When you’re done with the tutorial, test your knowledge by taking our article quiz!

The Upload Process

Uploading a photograph and shortening its link can be done a number of ways in the application we’re building. We have to first upload the image, retrieve a link to it, and then submit that link to a URL shortening service. If we automate the whole process so that it works as soon as the user posts their tweet, we risk a long time delay; that’s because it requires three calls to different servers. Instead we’ve chosen the following process:

  1. user uploads the photo
  2. link to the photo is inserted at the end of the tweet in the posting form
  3. message is then sent by the user

All code for this article is contained within the new file cheepcheep_image_flashbuilder.fxp, and you can download it together with all the resources from the previous article.

Browsing for Files

The ActionScript FileReference class gives us the ability to upload and download files from a Flash application. Its browse method uses an operating system dialog box to locate a file; each instance of FileReference will refer to a single file on the user’s system. Make sure that a namespace is added for net to the Application tag:

<s:Application ... 
 xmlns:net="flash.net.*"/>

Create an instance of FileReference between the <fx:Declarations></fx:Declarations> tags already in the application:

<net:FileReference id="fileReference" select="fileSelected(event)" complete="fileAccessed(event)" />

We’ve set function calls for its select and complete events that we’ll add later. Under the TextInput field for the tweet at the bottom of the application we’ve added a button; this enables us to browse for a file to upload and we’ll have that call the function browsePhoto, listed below:

private function browsePhoto(evt:MouseEvent):void { 
 var arr:Array = [];
 arr.push(new FileFilter("Images", ".gif;*.jpeg;*.jpg;*.png"));
 fileReference.browse(arr);        
}

This function creates an array and populates it with a FileFilter object to limit the file extensions the user can choose. It then calls the browse method of the FileReference instance, passing the array as an argument. This will launch an operating system dialog box that will open to the most recently browsed directory. The function for the select event will then fire once the user has selected a file. It loads the file, which in turn will fire the complete event that displays the name of the file to the user in our application. It’s in a new Label component that we added next to the browse button. You could use this function to display a preview of the image if required:

private function fileSelected(evt:Event):void {
 fileReference.load();
}

private function fileAccessed(evt:Event):void {
 fileName.text = "File selected: " + fileReference.name;
}

Authenticating with Flickr

There are a number of services that host photographs online for free. We’ve chosen Flickr because they have a well-documented API that already has a third-party ActionScript library. Flickr does require both application authentication and user permission before an application can use its services. The first item you’ll need is an API key for your application, which you can obtain for free; the key will also have a matching “secret” for authentication. Find out more about this here:

The ActionScript 3 Flickr library can be found at: http://code.google.com/p/as3flickrlib/. The last build of the library is without a method for uploading to Flickr, in either the library or the SWC. While there’s a version of an upload method in the project’s code repository, we’ve found issues in making it work. We’ve supplied a copy of the library with the Flash Builder project that has a working upload method; so far this is the only part of the library where we’ve experienced problems. The Flickr library uses additional MD5 and network utilities from http://code.google.com/p/as3corelib/ that you’ll also need to download, and we’ve included the SWC in the Flash Builder project.

The Flickr process of user authentication does involve a few steps programmatically, but it should be simple enough. Ultimately you need to generate a token on Flickr for each individual user of your application, and store that token on the user’s computer for each call to Flickr. This token is a secure representation of the user’s credentials; we can use it to perform tasks like our upload without having to know these credentials. The token is generated when the user grants permission to your application on Flickr.

We’re going to use the ActionScript SharedObject to store the authorization token locally, then test to see if it exists when we browse for a file. We’ll ask the user to authenticate the application if it doesn’t exist, and write the token and user account ID when it’s retrieved. Check the code below and you’ll see that we’re importing the necessary classes from the Flickr library, creating an instance of SharedObject and the variables to hold the multiple values we’ll use during the authentication process:

import com.adobe.webapis.flickr.events.FlickrResultEvent;  
import com.adobe.webapis.flickr.FlickrService;  
import com.adobe.webapis.flickr.methodgroups.Auth;  
import com.adobe.webapis.flickr.methodgroups.Upload;  
 
private var appSO:SharedObject;  
 
private var flickr:FlickrService;  
private var flickrApiKey:String = "yourFlickrApiKey";  
private var flickrSecret:String = "yourFlickrApiKeySecret";  
private var flickrFrob:String = "";  
private var flickrAuthToken:String = "";  
private var flickrNsid:String = "";  
private var authorizationURL:String = "";

In the getFlickrLoginCredentials function below we’re attempting to read the SharedObject for our application, creating it if it fails to exist. We’re then testing to see if the Flickr authorization token has been stored. If it’s not there we create an instance of the FlickrService, initializing it with the API key, adding the secret, assigning a handler, and calling a getFrob method. This calls Flickr to retrieve a “frob” that’s needed to fetch the authorization token — after the user has connected to Flickr and granted access permission to our application:

public function getFlickrLoginCredentials():void {  
 appSO = SharedObject.getLocal("TestFlickrTwitter");  
 if ( appSO.data.flickrAuthToken == null ) {  
   flickr = new FlickrService(flickrApiKey);  
   flickr.secret = flickrSecret;  
   flickr.addEventListener( FlickrResultEvent.AUTH_GET_FROB, onGetFrob );  
   flickr.auth.getFrob();  
 } else {  
   flickrAuthToken = appSO.data.flickrAuthToken;  
   flickrNsid = appSO.data.flickrNsid;  
   browsePhoto();  
 }  
}

The other half of the getFlickrLoginCredentials function is an else clause; it reads the Flickr authorization token and Flickr user id from the SharedObject, and calls the browsePhoto method we described before. We’ve changed the browse for photo button to now call getFlickrLoginCredentials instead, so that we’ll put the user through the authentication process if they try to upload a photograph.

Below is the onGetFrob function, the result handler called when we receive a result from the getFrob call in the function above. This stores the “frob” as a local variable and creates the authorization URL that will be needed later:

private function onGetFrob( evt:FlickrResultEvent ):void {  
 flickrFrob = evt.data.frob;  
 authorizationURL = flickr.getLoginURL(flickrFrob, "write");  
 currentState = "flickrAuthorise";  
}

The function above calls an additional state that we created for our application called flickrAuthorise. We’ll use this to display a new component that we built in Flash Catalyst; this component is a popup that contains a message to the user explaining that they need to connect to Flickr to authorize the application. The component also contains two buttons: the first to open a new browser window and connect to the Flickr authorization page, and the second to fetch the authorization token from Flickr.

You’ll notice the new state listed in the application:

<s:states>  
 <s:State name="twitterLogin"/>  
 <s:State name="twitterDisplay"/>  
 <s:State name="flickrAuthorise"/>  
</s:states>

Have a look at the visual objects in the application and you’ll see various attributes used to control them in several states. Take the browsePhotoBtn, for example; it uses the includeIn attribute to nominate which states it will appear in. An additional attribute to change the button’s alpha for the flickrAuthorise state has also been set so that this will dim when the authorization popup appears. Most of the other visual objects have the same attribute for the same effect:

<s:Button x="180" y="670" label="Browse for Photo" click="getFlickrLoginCredentials()" id="browsePhotoBtn" includeIn="flickrAuthorise,twitterDisplay" alpha.flickrAuthorise="0.4"/>

Clicking on the first button in our authorization component, CustomComponent2, calls a function in the main application, authoriseFlickr. This launches a new browser window calling the authorization URL created inside the onGetFrob function. It also enables the second button in the authorization component:

public function authoriseFlickr():void {  
 var urlRequest:URLRequest = new URLRequest(authorizationURL);  
 navigateToURL(urlRequest, "_blank");  
 customcomponent21.button3.enabled = true;  
}

The user then has to go through a couple of steps on the Flickr site to authorize the application. The last screen they’ll see will tell them that they can close the browser window; they then should click on the second button on the authorization component, which calls the onGetTockenClick function in the main application. This in turn calls the getToken method of FlickrService. A handler function, onGetToken is specified in the onGetTockenClick function. onGetToken will receive an object from Flickr containing the authorization token and some details about the user; we use that function to store both the token and user id locally, as well as in the SharedObject. We also switch back to the main twitterDisplay state:

public function onGetTokenClick():void {  
 flickr.addEventListener(FlickrResultEvent.AUTH_GET_TOKEN, onGetToken);  
 flickr.auth.getToken(flickrFrob);          
}  
 
private function onGetToken(evt:FlickrResultEvent):void {  
 flickrAuthToken = evt.data.auth.token;  
 flickrNsid = evt.data.auth.user.nsid;  
 appSO.data.flickrAuthToken = flickrAuthToken;  
 appSO.data.flickrNsid = flickrNsid;  
 appSO.flush();  
 currentState = "twitterDisplay";  
}

This will occur the first time a user attempts to browse for a photograph, so they’ll need to browse again after authentication as this step was interrupted by this process. Once we’ve located our photo we need to upload it so that we can include a link to it in our tweet.

Uploading the Photo

The adjusted upload class in the Flickr library ensures that the necessary authentication, upload process, and upload complete event are all taken care of. We’ve added a button at the bottom of the application below the Twitter post form to upload an image, and created a function for this button called uploadFlickr. This function creates a listener for the upload complete event and creates another instance of the FlickrService class, adding credentials to it. This is then used as the argument for an instance of the upload class that’s used to upload our fileReference, which was created when we browsed for a file. We’ve also added a call to the setBusyCursor method of the CursorManager to provide a simple form of feedback to the user while the upload progresses – as FlickrService is without this feature:

private function uploadFlickr():void {   
 fileReference.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA,uploadCompleteHandler);          
 flickr = new FlickrService(flickrApiKey);  
 flickr.secret = flickrSecret;  
 flickr.token = flickrAuthToken;  
 var uploader:Upload = new Upload(flickr);  
 uploader.upload(fileReference);  
 CursorManager.setBusyCursor();  
}

The upload will return an XML packet from Flickr containing an ID for the image uploaded, which we can utilize to construct a link to use in our tweet. With the 140-character limitation of Twitter, we need to manage this as Flickr URLs tend to be a little long. We’re going to work around this by using the URL shortening service bit.ly, which has a REST API that we can use in our application. You’ll need to create an account with bit.ly to receive an API key; this is automatically issued with the account, with your key on your account details page.

Creating a Shortened Link to the Image

The uploadCompleteHandler is specified as the result handler in uploadFlickr and will receive the XML in the data property of the event. We convert that into an XML object in the function and construct a URL combining a string, the Flickr user ID, and the photo ID. We’ve also created variables to store the bit.ly login and API key, both required for the rest call:

[Bindable] private var photoUrl:String = "";   
private var bitlyLogin:String = "yourBitlyAccountName";  
private var bitlyApiKey:String = "yourBitlyApiKey";  
 
private function uploadCompleteHandler(evt:DataEvent):void {  
 CursorManager.removeBusyCursor();  
 var xData:XML = new XML(evt.data);  
 photoUrl = "http://www.flickr.com/photos/"+flickrNsid+"/"+xData.photoid;  
 bitlyService.send();  
}

The uploadCompleteHandler function calls the send method of an HTTPService once the address for the image has been constructed. Remember that the HTTPService tag needs to be nested within a <fx:Declarations> tag set. The url attribute of the HTTPService tag needs to pass a number of arguments to bit.ly, all of which are bound to application variables. We’ve set a handler for the result event of the HTTPService tag:

<mx:HTTPService id="bitlyService" method="GET" url="http://api.bit.ly/shorten?version=2.0.1&amp;longUrl={photoUrl}&amp;login={bitlyLogin}&amp;apiKey={bitlyApiKey}&amp;format=xml" result="bitlyService_resultHandler(event)"/>

We allow Flash Builder to create a default handler to process the bit.ly result; the shortened URL is contained within the XML packet returned by the service, which it converted into an ActionScript object for us. The function calculates the length of the text currently in the TextInput field for the tweet and either appends the shortened URL, or truncates the text and then appends the URL:

protected function bitlyService_resultHandler(evt:ResultEvent):void   
{  
 var bitlyURL:String = evt.result.bitly.results.nodeKeyVal.shortUrl;  
 var combineTextURL:String = textinput1.text + " " + bitlyURL;  
 if ( combineTextURL.length < 140) {  
   textinput1.text = combineTextURL;  
 } else {    
   var excessChars:Number = 140 - combineTextURL.length;  
   textinput1.text = textinput1.text.substr(0,(textinput1.text.length)-Math.abs(excessChars)) + " " + bitlyURL;  
}  
}

With the image uploaded and a shortened link created for it we’re now ready to complete our tweet and post it to Twitter.

Uploading directly from within a Flash application was an often-requested feature introduced with Flash Player 9. Connecting images with text is a great enhancement to Twitter, and we’re sure you’ll want to take advantage of the popularity of photo-sharing services as part of the social experience afforded by your application. The Flickr API documentation is a little intimidating to the uninitiated, especially the authentication process. Fortunately, there are ActionScript libraries available to developers that make the process easier.

It’s likely that there are other ways to order the application processes described in this article. Twitter is not a one-stop shop––their service is built for posting minimal text messages. We’re relying on three service calls to generate our single Twitter post; the challenge is to bring them together in a way that’s easy for the user while offering reasonable performance times.

Make sure you download the code for this article and give it a try.

If you’re feeling confident, test your knowledge by taking our article quiz!

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.