Using AIRFacebook listener interfaces

Posted: September 10, 2015

In previous versions of the AIRFacebook ANE (v1.0.3 and older), the only way to retrieve results of various async requests was with the use of the standard Flash event model. You added your desired event to the dispatcher, created corresponding handler, made the request, removed the event from the dispatcher and played around with the event object that you received. After all, that is what AS3 devs have been doing for years, until signals happened.

All in all, nothing too wrong with that; except, it creates room for errors which in turn make the development slower. You have to know what event will be dispatched before making the request and you have to remove the event from the dispatcher so that your handler doesn’t receive result from a request you make later in other scope of your app.

So I thought about adding Function parameter to request methods that would serve as a callback. But the Function data type in AS3 doesn’t really give you any idea about the function’s signature so every request would involve checking the documentation to see what signature the callback should have. Since most of the AS3 IDEs provide great code completion and generation features, I decided to use interfaces to handle all the different request results. Thanks to these interfaces, you can either implement them in the class that makes the request or even create custom listener objects. Here is a quick look:

// Implements login listener so it can handle AIRFacebook's login response

public class LoginScreen implements IAIRFacebookLoginListener {

    ...

    private function onLoginButtonTriggered():void {
        /* Assuming AIRFacebook is initialized */

        // Passing 'this' as a listener

        AIRFacebook.loginWithReadPermissions( new <String>["birthday"], this );
    }

    ...

    /* Implemented methods of &#039;IAIRFacebookLoginListener&#039; */

    public function onFacebookLoginSuccess( deniedPermissions:Vector.<String>, grantedPermissions:Vector.<String> ):void {
        // Do your stuff

    }

    public function onFacebookLoginCancel():void {
        // Do your stuff

    }

    public function onFacebookLoginError( errorMessage:String ):void {
        // Do your stuff

    }

}

As you can see, it’s very simple to use the interface to implement login callbacks. You don’t have to worry about removing the listener (it’s used for that particular request only) and all the returned data is part of the defined method’s signature so there’s no need to explore any event object to see what it has to offer. And thanks to great IDE like IntelliJ IDEA all it takes is couple of keyboard taps to implement the appropriate listener. Also, you get a compile-time error checking. As of AIRFacebook v1.2 every request method accepts (optional) object that implements one of the listener interfaces. Of course, the standard events are still fully functional and available to use.

But, there is always a but… I should mention, there are cases where these listeners are bit of a drawback. Imagine that somewhere in your app you are working with Open Graph (for example, posting and getting user’s score). These are two different requests, yet they are handled by a single listener (IAIRFacebookOpenGraphListener – if implemented as in the example above). So unless you add some code inside your callbacks to check what data you’ve retrieved, you don’t know which request’s data you are currently working with. There are at least three solutions to this issue:

(1) Welcome back, events!

You still have the choice of using the good ol’ events and they’re probably the fastest way to solve this problem, since you can associate the requests with different handlers (as long as you make the next request after getting the result of the previous one).

(2) Control flag(s)

You could add a Boolean flag (isGetScoreQuery) and set it to true/false before making a request. That way you would know if the result is from the GET or the POST query (again, as long as the requests are sent one after another). If there are more requests, you could use an integer as a flag and use a switch statement to control the execution flow. Not the prettiest but gets the job done.

(3) Custom listener objects

If AS3 allowed for creating anonymous classes in Java like fashion this solution would be a lot simpler. Since we cannot do that, it involves a bit more code and is probably not worth doing in most cases, unless you want to make several requests at the same time and still want to know which request the returned data corresponds to. So this is where custom listener object comes into place. This object is simply an instance created by our screen class (or whoever makes the Open Graph request) and is used as a listener instead of the screen. We could create a private class in our screen like this:

public class OpenGraphScreen {

   ...

   private function onGetScoreButtonTriggered():void {
      // Passing our custom object as a listener

      AIRFacebook.requestScores( new GetScoreListener( this ) );
   }

   ...

   /* Called by our custom listener */
   public function onGetScoreSuccess( jsonResponse:Object, rawResponse:String ):void { }

   /* Called by our custom listener */
   public function onGetScoreError( errorMessage:String ):void { }

} // OpenGraphScreen class end



/* Inside OpenGraphScreen.as */
class GetScoreListener implements IAIRFacebookOpenGraphListener {

   private var mTarget:OpenGraphScreen;

   public function GetScoreListener( target:OpenGraphScreen ):void {
      mTarget = target;
   }

   /* Implemented methods of &#039;IAIRFacebookOpenGraphListener&#039; */
   
   public function onFacebookOpenGraphSuccess( jsonResponse:Object, rawResponse:String ):void {
      mTarget.onGetScoreSuccess( jsonResponse, rawResponse );
      mTarget = null;
   }

   public function onFacebookOpenGraphError( errorMessage:String ):void {
      mTarget.onGetScoreError( errorMessage );
      mTarget = null;
   }

}

You’d create another class PostScoreListener and use it in the same way as the GetScoreListener.