Time for action - creating an HTTP Requestor

The idea is, we move all of the code regarding the URLLoader from CustomGraphContainerController to a separate class, called HTTPRequestor. We will then replace the CustomGraphContainerController constructor with this:

public function CustomGraphContainerController(a_graphControlContainer:GraphControlContainer)
{
super(a_graphControlContainer);
_requestor = new HTTPRequestor();
_requestor.request(new GraphRequest("PacktPub"));
}

Why bother? Well, apart from being neater, there are two main advantages:

  1. It's much simpler to request several Graph Objects or Graph Lists; no need to deal with multiple instances of URLLoader.
  2. In the next chapter, we'll see how to use the official Adobe AS3 Facebook SDK to retrieve information from the Graph API. If all the code for a request is encapsulated in one class, then we only need to change one line to switch from using HTTP to using Adobe's SDK:
    public function CustomGraphContainerController(a_graphControlContainer:GraphControlContainer)
    {
    super(a_graphControlContainer);
    _requestor = new SDKRequestor();
    _requestor.request(new GraphRequest("PacktPub"));
    }
    

Note

GraphRequest is a simple class; its constructor allows you to use two parameters to specify what you'd like to retrieve from the Graph API:

  • objectID, the name of any Graph Object.
  • connectionID, the name of any connection of that Graph Object.

So, to request the Packt Publishing Page, you would use this GraphRequest:

newGraphRequest("PacktPub");

and to request the list of Posts from the Packt Publishing Page, you'd use this:

newGraphRequest("PacktPub", "posts");

The class is already written; it's in \src\com\graph\apis\http\HTTPRequestor.as. Take a look! There are a few changes from the code we wrote in CustomGraphContainerController.as, but these all have comments to explain them:

package graph.apis.http
{
import events.DialogEvent;
import events.RequestEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.HTTPStatusEvent;
import flash.events.IEventDispatcher;
import flash.events.IOErrorEvent;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.net.URLVariables;
import flash.utils.Dictionary;
import graph.apis.base.IRequestor;
import graph.BaseGraphItem;
import graph.GraphList;
import graph.GraphObject;
import graph.GraphRequest;
import com.adobe.serialization.json.JSON;
//the class needs to dispatch events (see later in code for why)
public class HTTPRequestor extends EventDispatcher implements IRequestor
{
//this is used to figure out which GraphRequest created each //loader
private var _requests:Dictionary = new Dictionary();
public function HTTPRequestor(target:IEventDispatcher = null)
{
//this is needed because the class extends EventDispatcher
super(target);
}
public function request(a_request:GraphRequest):void
{
var loader:URLLoader = new URLLoader();
var urlRequest:URLRequest = new URLRequest();
var variables:URLVariables = new URLVariables();
//We construct a URL from the parameters of the GraphRequest
urlRequest.url = "https://graph.facebook.com/" + a_request.objectID;
if (a_request.connectionID)
{
urlRequest.url += "/" + a_request.connectionID;
}
variables.metadata = 1;
urlRequest.data = variables;
//this is used to figure out which GraphRequest created the loader later
_requests[loader] = a_request;
loader.addEventListener(Event.COMPLETE, onGraphDataLoadComplete);
loader.load(urlRequest);
}
private function onGraphDataLoadComplete(a_event:Event):void
{
var loader:URLLoader = a_event.target as URLLoader;
var graphData:String = loader.data;
var decodedJSON:Object = JSON.decode(graphData);
//we find the original GraphRequest used to start the loader
var originalRequest:GraphRequest = _requests[loader] as GraphRequest;
if (decodedJSON.data)
{
var graphList:GraphList = new GraphList();
var childGraphObject:GraphObject;
for each (var childObject:Object in decodedJSON.data)
{
childGraphObject = new GraphObject();
for (var childKey:String in childObject)
{
childGraphObject[childKey] = childObject[childKey];
}
graphList.addToList(childGraphObject);
}
graphList.paging = decodedJSON.paging;
//we use the properties of the original GraphRequest to add //some extra data to the GraphList itself
graphList.ownerID = originalRequest.objectID;
graphList.connectionType = originalRequest.connectionID;
//since this class does not have a renderGraphList() method, //we dispatch an event, which CustomGraphContainerController //will listen for, and call its own renderGraphList() method
dispatchEvent(new RequestEvent(RequestEvent.REQUEST_COMPLETED, graphList));
}
else
{
var graphObject:GraphObject = new GraphObject();
for (var key:String in decodedJSON)
{
graphObject[key] = decodedJSON[key];
}
//since this class does not have a renderGraphList() method, //we dispatch an event, which CustomGraphContainerController //will listen for, and call its own renderGraphList() method
dispatchEvent(new RequestEvent(RequestEvent.REQUEST_COMPLETED, graphObject));
}
}
}
}

There's no need to change any of this, or even to understand any of it apart from the HTTP request code that we wrote earlier. Just remember, its purpose is to encapsulate your requests to the Graph API.

Now, go back to CustomGraphContainerController.as and remove all the request-related code:

package controllers
{
import ui.GraphControlContainer;
public class CustomGraphContainerController extends GCController
{
public function CustomGraphContainerController (a_graphControlContainer:GraphControlContainer)
{
super(a_graphControlContainer);
}
}
}

CustomGraphContainerController inherits a protected variable called _requestor of type IRequestor, as well as a method for adding the required event listeners to it, so all we need to do is this:

package controllers
{
import graph.apis.http.HTTPRequestor; import graph.GraphRequest; 
import ui.GraphControlContainer;
public class CustomGraphContainerController extends GCController
{
public function CustomGraphContainerController (a_graphControlContainer:GraphControlContainer)
{
super(a_graphControlContainer);
_requestor = new HTTPRequestor(); addEventListenersToRequestor(); _requestor.request(new GraphRequest("PacktPub"));
}
}
}

Compile and run your SWF, then expand the Connections box and click on "posts":

Time for action - creating an HTTP Requestor

Great! The Graph List Renderer appears, with a black line to the Page to indicate that there is a connection between them. What about the other connections? Try clicking on statuses.

Error #2044: Unhandled ioError:.text=Error #2032: Stream Error. URL: https://graph.facebook.com/204603129458/statuses?metadata=1

Oops.

What just happened?

If you load the troublesome URL in your browser (https://graph.facebook.com/packtpub/statuses), you'll see the following message:

{
"error": {
"type": "OAuthAccessTokenException",
"message": "An access token is required to request this resource."
}
}

This error is due to not being logged in to Facebook through your SWF. We'll look at how to solve this in the next chapter.

Note

For now, you can get around the error by adding an IO_ERROR event listener to the URLLoader. In HTTPRequestor.as, modify request():

public function request(a_request:GraphRequest):void
{
varloader:URLLoader = new URLLoader();
varurlRequest:URLRequest = new URLRequest();
varvariables:URLVariables = new URLVariables();
//We construct a URL from the parameters of the //GraphRequest
urlRequest.url = "https://graph.facebook.com/" + a_request.objectID;
if (a_request.connectionID)
{
urlRequest.url += "/" + a_request.connectionID;
}
variables.metadata = 1;
urlRequest.data = variables;
//this is used to figure out which GraphRequest //created the loader later
_requests[loader] = a_request;
loader.addEventListener(Event.COMPLETE, onGraphDataLoadComplete);
loader.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
loader.load(urlRequest);
}

You will need to import flash.events.IOErrorEvent. Now, in the same class, create a simple event handler function to trace the error:

private function onIOError(a_event:IOErrorEvent):void
{
trace(a_event.text);
}

Note

This way, you can see the error in your output window, but it won't crash the SWF. Note: a try-catch block will not work for this kind of error.