AS3 Yammer Library

For the past few months I have been working on an AS3 library that wraps the Yammer API for use in Flash, Flex, and AIR projects. The biggest hurdle was making the application work for the web. As it is now, for Flash projects, you still need to be targeting Flash player 10 so that you can upload the ByteArray information file attachments. The second hurdle was creating an easy Oauth flow that doesn’t add items to the header that would violate the Flash player security, something you don’t have to worry about when creating AIR applications.

With that work out of the way, I would like to introduce an AS3 Yammer API library that communicates with the Yammer service. The as3yammerlib is hosted on GitHub and the source code is publicly available. To use the library you will need your own consumer key and secret for the Yammer API available from Yammer’s developer site. The library is also dependent on the as3corelib for doing JSON parsing and as3-signals for broadcasting events.

I put together a Flex web-based example using Flex Builder 4 that walks you through the Oauth process and retrieves messages for the logged in user. To use the code, just create a new Flex project and paste the Main.mxml file in your main application file and create another mxml for the MessageItemRenderer file within the same directory:

Main.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/halo"
               xmlns:ns="library://ns.adobe.com/flex/mx"
               creationComplete="init()"
               minWidth="1024" minHeight="768">


    <fx:Script>
        <![CDATA[
            import com.yammer.api.Yammer;
            import com.yammer.api.events.YammerEvent;
            import com.yammer.api.vo.Oauth;
            import com.yammer.api.vo.YammerCurrentUser;
            import com.yammer.api.vo.YammerError;
            import com.yammer.api.vo.YammerMessageList;
            import com.yammer.api.vo.YammerTab;

            import mx.collections.ArrayCollection;
            import mx.controls.Alert;

            private var oauthToken:String;
            private var oauthSecret:String;

            // your consumer key and secret go here
            private var consumerKey:String = "your consumer key";
            private var consumerSecret:String = "your consumer secret";

            private var service:Yammer;

            [Bindable] private var currentUser:YammerCurrentUser;

            private function init():void
            {
                this.service = new Yammer(this.consumerKey, this.consumerSecret);
                this.service.errorReceived.add(errorReceived);

                // we have tokens stored
                if(oauthToken && oauthSecret){
                    service.setOuthTokens(oauthToken, oauthSecret);
                    this.getCurrentUser();
                }
            }

            // get request token from service.
            private function getRequestToken():void
            {
                service.resultsReceived.add(requestTokenReceived);
                service.requestToken();
            }

            // submit the virify pin from the web site
            private function getAccessToken(verify_pin:String):void
            {
                service.resultsReceived.add(accessTokenReceived);
                service.accessToken(verify_pin);
            }

            // sends user to browser page to retrieve verification pin.
            private function sendAuthorizationRequest():void
            {
                service.sendAuthorizationRequest(); // open browser window
            }

            private function getCurrentUser():void
            {
                service.resultsReceived.add(currentUserReceived);
                service.getCurrentUser();
                return;
            }

            public function getMessages(tab:YammerTab):void
            {
                service.resultsReceived.add(messageListReceived);
                service.getMessages(tab.url);
                return;
            }

            private function requestTokenReceived(value:Oauth):void
            {
                service.resultsReceived.remove(requestTokenReceived);
                Alert.show("Request tokens received: " + value.oauth_token);
            }

            private function accessTokenReceived(value:Oauth):void
            {
                service.resultsReceived.remove(accessTokenReceived);
                Alert.show("Access tokens received:: token: " + value.oauth_token + " secret: " + value.oauth_token_secret);
                this.getCurrentUser();
            }

            private function currentUserReceived(value:YammerCurrentUser):void
            {
                service.resultsReceived.remove(currentUserReceived);
                currentUser = value;
                var tab:YammerTab = currentUser.tabs[0]; // get first user tab, usually My Feed
                this.getMessages(tab);
            }

            private function messageListReceived(value:YammerMessageList):void
            {
                service.resultsReceived.remove(messageListReceived);
                var messagelist:YammerMessageList = value;
                var messages:ArrayCollection = new ArrayCollection(messagelist.getMessages());
                messageList.dataProvider = messages;
            }

            private function errorReceived(error:YammerError):void
            {
                Alert.show("Yammer Service Error: " + error.errorMessage);
            }

        ]]>
    </fx:Script>

    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
    </fx:Declarations>

    <s:Button label="1. Request Token" click="getRequestToken()" width="120" x="10" y="10"/>

    <s:Button label="2. Open Browser" click="sendAuthorizationRequest()" width="120" x="138" y="10"/>

    <s:TextInput id="pinInput" text="3. Paste Pin" width="120" textAlign="center" focusIn="pinInput.text = ''" x="266" y="10"/>

    <s:Button label="4. Access Token" click="getAccessToken(pinInput.text)" width="120" x="394" y="10"/>

    <s:List id="messageList" x="10" y="113" width="563" height="337" itemRenderer="MessageItemRenderer"/>

    <ns:Image source="{currentUser.mugshot_url}" width="48" height="48" x="11" y="45"/>

    <s:Label text="{currentUser.full_name}" x="67" y="50"  fontSize="14"/>

    <s:Label text="{currentUser.job_title}" x="67" y="72"  fontSize="11"/>

</s:Application>

MessageItemRenderer.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer width="100%"
           xmlns:fx="http://ns.adobe.com/mxml/2009"
           xmlns:s="library://ns.adobe.com/flex/spark"
           xmlns:mx="library://ns.adobe.com/flex/halo" xmlns:ns="library://ns.adobe.com/flex/mx">

    <fx:Script>
        <![CDATA[
            import com.yammer.api.utils.MessageUtilities;
            import com.yammer.api.vo.YammerMessage;

            [Bindable] private var message:YammerMessage;

            override public function set data(value:Object):void
            {
                super.data = value;
                message = data as YammerMessage;
            }

            /**
             *  override set data to set
             */
            override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
            {
                super.updateDisplayList(unscaledWidth,unscaledHeight);
            }
        ]]>
    </fx:Script>

    <s:Rect left="0" bottom="1" right="0" height="1">
        <s:fill>
            <s:SolidColor color="0x000000" alpha=".2"/>
        </s:fill>
    </s:Rect>

    <s:HGroup width="100%" height="100%" paddingTop="5" paddingBottom="5" paddingRight="5" paddingLeft="5">

        <ns:Image source="{message.sender.mugshot_url}" width="48" height="48"/>

        <s:VGroup width="100%" height="100%">

            <s:Label  text="{message.sender.full_name}" maxDisplayedLines="1" left="10" top="10" right="26"  fontWeight="bold" color="#000000"/>

            <s:RichEditableText id="richEdTxt" text="{message.body_plain}" width="100%" editable="false" selectable="false" focusEnabled="false" />

            <s:HGroup width="100%" paddingTop="2">
                <s:Label text="{message.created_at}"/>
            </s:HGroup>

        </s:VGroup>

    </s:HGroup>

</s:ItemRenderer>

I am sure there are lots of ways to improve the library, but this one is a pretty simple start with lots of functionality. Please let me know if you find any errors and feel free to fork this version and add any improvements to the code.

-Mr

HTTP Status 201 causing Flex #2032 Error in IE only

I ran into a problem right when we were going to launch a new Flex web-based application to production (of course) with Internet Explorer and Flex. I was calling an RESTful service that returns XML and using the built-in Flex HTTPService with either POST/GET as the method for the request. Most of the service calls either return a HTTP Status Code of 200 (OK) or they throw an error. For one POST request that returned a HTTP Status of “201 Created” Flex threw up a 2032 Stream Error in response. Here is a brief definition of the error:

The referenced resource does not exist or the swf is trying to access a file across a restricted domain.

This error causes Flex to report a fault error on the request even though it was successful. The 201 status code is handled fine in all other browsers except IE (6 & 7). I did some research on the issue thinking it must be something easy to fix, that many others have run across before me. It seems the error is out there for lots of different reasons.

One post reported that IE was having a caching issues, and the way to solve it was to add a random number on the end of your service calls or you have to set the response headers in your server side page to prevent caching. This one sounds similar to a FireFox issue I ran into a couple years back, but didn’t effect the results:

http://www.judahfrangipane.com/blog/?p=87

The overwhelming number of posts on the subject suggested that Rails and Adobe both share the responsibility for this error (depending on if you are a Rails dev or a Flex dev, the debate rages as to who is to blame). The fix seems to be changing status returned from Rails to something other than 201. This seems to be the legit solution since the web service I was hitting is running Rails:

http://stackoverflow.com/questions/354936/flex-2032-stream-error-in-ie-only
http://nachbar.name/blog/2008/06/14/flex-railsprotect_from_forgery-problem-with-rails-21-produces-ioerror-2032/
http://onrails.org/articles/2008/02/20/dealing-with-http-errors-in-a-flex-with-rails-application
http://www.manning-sandbox.com/message.jspa?messageID=64372
http://nachbar.name/blog/tag/adobe-flex-programming/

If you think Adobe should fix the issue (as some Rails people protest) then you can view all the other bug reports Adobe already had lined up to fix that involve a 2032 Stream Error, just type in Explorer 2032 and you will find them:

http://bugs.adobe.com/jira/secure/QuickSearch.jspa

The workaround we ended up implemented was totally client based. In the fault handler of this particular service request, I look for the errorID value of the Fault event and ignore it. It was all I could do on short notice and I wasn’t about to convince the backend dudes that we needed to change the server response from 201 to 200, pick your battles and my first battle was to get this app into the war.

For me the real culprit is IE (easy target). Something that works in every other browser but IE is so friggen familiar to anyone who has done any cross-browser work. IE sucks, it has always sucked, and it will always suck, period. My other thought is about web services and codes in general. As an application developer, I use a lot of different services, consuming data from many different pipes. I couldn’t tell you as a consumer of web services and an application designer what the value of having a returned Status of 201 has over returning a Status of 200, can you? I make a request, and if the request is good, just tell me ok, I don’t need to know that the server returned a HTTP Status Code of 311 (the server code for “I just made you a sandwich”). I need to know only two basic things, was the request good, or did it explode?

I think some API dudes and backend dudes are purists, they want server codes to bubble up to the client so we can admire them, even though in practice (meaning you actually build applications with these services) you don’t really need the extra data. Hey, I am totally used to having to make 5 service calls to accomplish one thing with a web service, I would never ask for the service to be changed to convenience the client, but some information is superfluous and could be scaled down.

UPDATE

Saw another blog post about the same topic at UserFlex.

- Mister

PhoneSeek an Adobe AIR application to track GPS enabled mobile devices

PhoneSeek is an application to track any device that uses InstaMapper. InstaMapper is free real-time GPS tracking service. To use InstaMapper you need a GPS enabled device with the free InstaMapper tracking software installed along with a API Key from InstaMapper’s web site for each device you own. You can track mobile phones (iPhone, G1, Blackberry) or even automobiles.

Use Case

With PhoneSeek I can add my InstaMapper API Key for my phone and track it from the desktop. I can also add and track multiple devices. On my G1 phone, I installed the Android version of the InstaMapper tracking software. This software allows me to send my phone’s GPS data to InstaMapper service where I can view it either on their website or consume the data through InstaMapper’s web service. In PhoneSeek I just add the API Key to begin tracking the advice as soon as your phone starts to transmit GPS data.

Get PhoneSeek

Download the latest version from this location.

Development Background

PhoneSeek might be used to track devices from your family members, or maybe track down a lost or stolen phone. However, Phoneseek was basically created as a test project to learn PureMVC and also experiment with the new Google Maps API SWC for Adobe AIR. Previous versions of the Google Maps API SWC only worked for web-based applications.

Coming from Cairngorm to PureMVC was quite an experience. At first it was really painful because I had a tendency to over think things, or try to structure things more the Cairngorm ways (like dude, where are my delegates and commands).

I really had a hard time understanding where to put certain code, like update code, or code to set the window location on startup, and even how to pass around value objects used by more than one view. It took some time to get used to the PureMVC way, but all in all, its not a bad framework to work with. However, I still prefer Cairngorm and have since started to investigate and really enjoy Mate.

Credit for some of the graphics goes to DragonArtz Designs.

- Mister

SuperImage Redux, caching images in Flex List

I have been meaning for so long to post this code. Some time ago I had the pleasure of working with John Yanarella from Universal Mind. John was helping my employer at the time to put together an application that allows users to upload, manage, and share media assets. We needed an efficient way to cache and display images to optimize the performance of loading and viewing a large number of thumbnails.

SuperImage

Last year Ely Greenfield posted on his blog QuietlyScheming a way to end images from flickering when you display them in a Flex List control. Ely created a new component called SuperImage that replaces the Flex Image control. SuperImage fixes a few issues with the current Image control layout in addition to adding the ability to cache loaded images to stop them from constantly reloading (causing the flicker when scrolling a list).

SuperImage Update

In our project we wanted use SuperImage, but what we wanted was for SuperImage to behave more like the Flex Image control. Specifically we wanted the control to broadcast the same standard events as the Image control; ioError, securityError, imageComplete, progress, completem, completeEffect. The new SuperImage also implements IDataRenderer, IDropInListItemRenderer, and IListItemRenderer interfaces. John Yanarella did a great job cleaning up the SuperImage control and add the missing functionality. We ended up not using the SuperImage and instead used a simpler implementation for caching the Image control.

Example

Below is an example similar to Ely’s SuperImage that demonstrates the problem (on the left) with the Image control and the fix (on right) using the updated SuperImage. This example also shows a text dump of events broadcast from the new SuperImage control.

Limitations

One thing the updated SuperImage still lacks is the ability to display loaded SWF files. Since our company never used the code in any project and it was based on Ely’s code, I thought it only fair to repost the update and the code for everyone to use as needed. If anyone has an further updates to the code or suggestions to fix the SWF loading issue, please post them here and I will continue to update the component.

Code

You can download the code for the above project here. You will need your own Flickr API key to make the application work. Be sure to also read Ely’s original post to see the other benefits of SuperImage. Many thanks to John Yanarella who actually did the coding on the project, I finally posted it as I promised him many months ago :) .

-Mister

Adobe Share

Adobe recently launched a new beta application called Share that allows you to upload and share files.  You can share file to a rostered list, the URL to the file, or embed the file on your web page (as I have done below).   Adobe also has a Share API that uses simple REST-based protocol for developers to build their own applications or mashups with using Share.   I wasn’t clear from the documentation how the commercial pricing will work or if building an application for others to manage or share their files will count against your account or bandwidth.  In fact, I am not sure what the pricing schema is for Share. 

Another puzzling thought is why did Adobe develop share to begin with?   You have a lot of existing vendors in the storage service provider arena already, in fact, you have many that use Adobe’s existing software to develop tools around storage and sharing.  So it is strange that a software company decided to compete with companies that already use its software to provide the same service (Bluestring by AOL is one example).   Does this mean that Adobe intends to become an online service for storage, document editing (Buzzword) and media editing (Remix and Photoshop Express)?  

I mean, its cool that the applications Adobe builds use their own software (Flex, Flash, etc.), but when they start competing with other companies using also using Adobe software to provide  similar services (aka, the bread and butter of those organizations), then I am wondering what will be the outcome? Is Adobe to become a huge conglomerate that not only provides services and software, but competes with its own food source for resources? What’s next, Adobe phones?  Of course I am joking.  I love Adobe and I love their products.  I guess as Flex developer, I’m just comfortable with Adobe providng me with the tools to create applications and not competing with the tools I create.  I could totally be misreading this move by Adobe, which is most likely the case (I say this so that a big black panel van with tinted windows doen’t pull up beside me one day and take me away).

Well, time will tell for sure.  The storage business is extremely expensive (bandwidth, banks of servers) and there are a lot of established companies already out there with storage and sharing services that offer more space and possibly a lower cost (Xdrive, Box, Mozy, Bluestring, Amazon S3) with sharing capabilities and API’s.  For now, the Share API looks to be a simple to use and easy service providing minimal sharing capabilities and less complexity than some other API’s. 

Flex Application to grab Youtube Videos (Updated to Flex 4)

A have been checking out a recent application called Scrapblog and looking under the hood at how the put the application together. I know they use XML to markup the coordinates and properties of media elements on the page.  What surprised me was that they take a Bitmap image of each slide and save that as a single JPEG file when you publish your Scrapblog.   I had just assumed they copy images from Flickr to the server and referenced those images in the published content.   I even thought they might dynamically link the images from Flickr.  I did notice though, that Scrapblog links to Youtube videos dynamically. 

They are obviously not using the Youtube API because Youtube blew that up by making a security policy change to the crossdomain.xml file needed to pull videos into Flex.   So I did a little investigation.  I found a site, in French, that had a Flash 8 application to basically screen scrape and decode the URL to Youtube videos.   I migrated this over to Flex and created a small demo application.

Here is the code for the application:

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">

    <fx:Script>
        <![CDATA[
            import mx.rpc.events.FaultEvent;
            import mx.rpc.events.ResultEvent;

            private var page:XML;
            private var _src:String;

            [Bindable]
            private var youtube:String;


            private function getYoutube(event:Event):void
            {
                _src = youtubeURL.text;
                scrape.text = "";
                youtubeService.url = _src;
                youtubeService.send();
            }

            private function getVideoID(src:String):String
            {
                var id:String = src.substr(src.indexOf("v=")+2);
                var nextVarPos:Number = id.indexOf("&");
                if(nextVarPos == -1)
                    return id;
                return id.substr(0,nextVarPos);
            }

            private function onResult(event:ResultEvent):void
            {
                //scrape.text = "\n" + scrape.text + "Screen result: \n\n" + event.result + "\n";

                var xml:String = event.result as String;
                var index1:Number = xml.indexOf('var swfConfig =');
                var index2:Number =  xml.indexOf('video_id', index2);

                var str:String = xml.substr(index1,  index2)

                var t:String = str.substr(str.indexOf("\"t\": ")+5);
                t = t.substr(0, t.indexOf(",") );

                if(t != "" && t.length > 0) {

                    youtube = "http://youtube.com/get_video.php?video_id=" + getVideoID(_src) + "&t=" + t;

                    scrape.text = "Youtube URL: " + youtube;
                    youtubePlayer.source = youtube ;
                    youtubePlayer.play();
                } else {
                    scrape.text = "There was an error parsing the contents of the page, we could not find a SWFObject.";
                }
            }


            private function onFault(event:FaultEvent):void
            {
                scrape.text = " \n Error: " + event.fault;
            }
        ]]>
    </fx:Script>

    <fx:Declarations>
        <s:HTTPService id="youtubeService" resultFormat="text"
                       fault="onFault(event)" method="GET"
                       result="onResult(event)"/>
    </fx:Declarations>


    <s:TextInput id="youtubeURL" text="http://www.youtube.com/watch?v=IpVWZePn1Y8" x="133" y="74" width="375"/>
    <s:Label x="42" y="76" text="Youtube URL:" fontWeight="bold"/>
    <s:Label x="33" y="129" text="Youtube Video:" fontWeight="bold"/>
    <s:Label x="42" y="421" text="Screen Scrape:" fontWeight="bold"/>
    <s:Button x="516" y="74" label="Get" click="getYoutube(event)"/>
    <s:VideoDisplay  id="youtubePlayer"  x="133" y="129" width="320" height="240"/>
    <s:Label x="42" y="10" text="Visit the page that contains the video, copy the URL on the right side of the page, and paste it in the field below." width="520" height="56" fontWeight="bold" fontSize="14"/>
    <s:Label x="53" y="377" text="* Be patient as screen scrapping a site is not always a fast process.  It may even blow up!"/>
    <s:TextArea x="133" y="420" width="429" height="146" id="scrape"/>

</s:Application>

To use it, just grab the URL for a Youtube video and past in the application (i.e. http://www.youtube.com/watch?v=IpVWZePn1Y8). The application actually parses the URL and takes the last bit after the “v=” to construct another URL to the actual FLV file.

I don’t think this is the most practical approach to grabbing Youtube videos and I am not entirely sure this is the only method, I wanted to put the application out there for comments and suggestions.   Another suggested solution was to use the BrightCove API.  Here is my updated attempt at the grabber using screen scraping:

Youtube Grabber (right-click to see source)

Update 7/17/2010

I found an excellent blog post similar to mine, it was posted sooner and works a bit better. Check out YouTube FLV URL post by Abdul. This one works great for getting the URL to Youtube videos.

I have also updated the application to work with Flex 4 and fixed an issue with the screen scrapper. Screen scraping isn’t the best approach because if Google changes the embed code (like they did recently), your screen scrapper will break and have to be updated. Plan on updating the application often as Google makes changes to the way they embed FLV files.

However because of security restrictions, the example will not run in the browser without a proxy. It will run from the Flex IDE and from an AIR application that can by-pass the Flash security sandbox.