Getting started with AIRFacebook API

Posted: September 13, 2015

The Facebook platform offers a number of features that we can easily implement using AIRFacebook ANE and provide great social experience for our users. In this post, we’ll take a look how to use the ANE to perform the following tasks in our app:

If your app is not configured with Facebook, please, read a previous post on how to set up an AIR app with Facebook.

ANE initialization

The very first thing to do is initialize the native Facebook SDK. It’s recommended to do so soon after our app is launched so somewhere in our main/document class we use the following snippet:

if( AIRFacebook.init( "{FACEBOOK_APP_ID}", false, null, true, this, this, this ) ) {
    trace( "Initialized extension context" );
} else {
    trace( "Failed to initialize extension context" );
}

Now let’s take a look what are all those parameters.

  1. Our Facebook app ID, nothing too special about that.
  2. Boolean that defines if we want the ANE to automatically log events (to Facebook server) once our app activates (i.e. user is using it) and deactivates (i.e. it’s put to background or shut down). We’re not interested in these logs at the moment so we pass in false.
  3. Third parameter is a URL scheme suffix and it is valid for iOS only. This value can be useful when having two different apps using single Facebook app ID – more about shared app ID is available at Facebook docs. We won’t use it right now.
  4. Next parameter is Boolean that we’ll want to set to true during development as it enables log messages.
  5. Last three parameters accept objects which implement IAIRFacebookCachedAccessTokenListener, IAIRFacebookBasicUserProfileListener and IAIRFacebookSDKInitListener interfaces. The easiest way to provide such object is to implement those interfaces in our main class and simply pass in the value of this which references instance of our main class. More about the idea behind this interface API is available in the post Using AIRFacebook listener interfaces.
If we don’t want to use these interfaces, we can always use standard event listeners. See the API reference to find out what events are dispatched after particular API calls.

So we make our class implement the aforementioned interfaces and add the required methods. Our main class will look something like this:

public class Main extends Sprite implements IAIRFacebookCachedAccessTokenListener, IAIRFacebookBasicUserProfileListener, IAIRFacebookSDKInitListener {

	...

	public function Main() {
	    if( AIRFacebook.init( "{FACEBOOK_APP_ID}", false, null, true, this, this, this ) ) {
	        trace( "Initialized extension context" );
	    } else {
	        trace( "Failed to initialize extension context" );
	    }
	}

	...

	/**
	 * AIRFacebook handlers
	 */
	
	public function onFacebookCachedAccessTokenLoaded():void {
	    trace( "Access token was loaded from cache" );
	}

	public function onFacebookCachedAccessTokenNotLoaded():void {
	    trace( "Access token was NOT found" );
	}

	public function onFacebookBasicUserProfileReady( user:BasicUserProfile ):void {
	    trace( "Basic user profile is ready" );
	}
	
	public function onFacebookSDKInitialized():void {
	    trace( "Facebook SDK initialized", AIRFacebook.sdkVersion );
	}

}
It’s recommended to wait for the SDK to initialize, either by adding AIRFacebookEvent.SDK_INIT event listener before calling AIRFacebook.init() or by implementing IAIRFacebookSDKInitListener.

After the Facebook SDK intializes, we’ll get a response if a cached access token was loaded or not. That way we know if user has logged in before and doesn’t have to do it again this time around. Also, if a cached access token exists, user’s basic profile will become available.

User login

If a cached access token was not loaded after initialization, we’ll have to log our user in before we start using the Facebook API on user’s behalf. First, we have to determine what type of requests we’ll want to execute and ask for the correct permissions based on that:

  • Reading – for example, asking for user’s information like email and friends.
  • Publishing – for example, updating user’s status or posting a new high score.

During each login attempt we can only ask for permissions which all belong to the same group; we cannot ask user for email and publish permissions in a single call – these have to be separate. The idea is to ask for only those permissions that are necessary at one particular moment. After we figure out the permissions we want we simply call one of the login methods:

/* Read only permissions */
AIRFacebook.loginWithReadPermissions( new <String>["email", "user_friends"], this );

...

/* Publish only permissions */
AIRFacebook.loginWithPublishPermissions( new <String>["publish_actions"], this );

We are passing in reference to this which means we’ll need to implement IAIRFacebookLoginListener interface.

/* Implemented methods from IAIRFacebookLoginListener */

public function onFacebookLoginSuccess( deniedPermissions:Vector.<String>, grantedPermissions:Vector.<String> ):void {
    trace( "Login successful: denied permissions:" + deniedPermissions + " | granted permissions: " + grantedPermissions );
}

public function onFacebookLoginCancel():void {
    trace( "Login cancelled" );
}

public function onFacebookLoginError( errorMessage:String ):void {
    trace( "Login error: " + errorMessage );
}

The successful callback gives us list of permissions which were denied/granted by the user during the last login attempt. We can check them to determine if we’re allowed to do what we intended in the first place and control our application flow appropriately.

If we need another permission later somewhere in our app, we can call the corresponding login method again – user won’t have to enter his password, instead he’ll be prompted with a dialog that allows him to grant or deny the new permission(s).

We can use properties AIRFacebook.deniedPermissions and AIRFacebook.grantedPermissions to check for all permissions that user has denied/granted to our app.

Displaying user profile

Now that the user has logged in, we can finally start doing some serious stuff! Let’s begin with the basics like displaying user’s name and profile picture. Important thing to note is that user’s profile is not available immediately after logging in, thus accessing AIRFacebook.basicUserProfile inside of our login handler would get us nowhere – the property will return null (unless the user wasn’t logging in for the first time but only answering a new permission request). When user logs in for the first time then the listener object we set for basic user profile in AIRFacebook.init() will be notified (and only this one time). If user logs out then it’s a good idea to add the listener again to know when the basic profile is ready for us to access:

/* Add listener object that implements IAIRFacebookBasicUserProfileListener */
AIRFacebook.addBasicUserProfileListener( this );

...

/* Callback method defined by IAIRFacebookBasicUserProfileListener */
public function onFacebookBasicUserProfileReady( user:BasicUserProfile ):void {
    AIRFacebook.removeBasicUserProfileListener( this );

    trace( "Basic user profile is ready" );
}
The BasicUserProfile instance returned in the callback is globally accessible using AIRFacebook.basicUserProfile so there’s no need to store the object manually.

Now we have some user information available but we are still missing the profile picture. We can get the picture URL using AIRFacebook.requestUserProfilePicture() and load the returned value using flash.display.Loader class, or we can ask the ANE to do it for us:

AIRFacebook.requestUserProfilePicture( 200, 200, true, this );

The first 2 parameters define the size of the picture, third parameter tells the ANE to load the picture for us and the last parameter is an object that will be notified once the picture is loaded (or if an error occurs). This object must implement IAIRFacebookUserProfilePictureListener interface.

/* Callback methods defined by IAIRFacebookUserProfilePictureListener */
public function onFacebookUserProfilePictureSuccess( picture:Bitmap ):void {
    trace( "Profile picture loaded" );

    /* Display the picture */
    ...
}

public function onFacebookUserProfilePictureError( errorMessage:String ):void {
    trace( "Profile picture error: " + errorMessage );
}

Now let’s say we want to know user’s birthday and gender. As you can see, BasicUserProfile instance doesn’t have these properties. That’s because Facebook doesn’t give us access to such information by default and we have to explicitly ask for it. We can use AIRFacebook.grantedPermissions to check if user have granted us birthday and gender permissions, otherwise we have to use the AIRFacebook.loginWithReadPermissions() method and ask for them. After we’re given these permissions, we can make the following request:

AIRFacebook.requestExtendedUserProfile( new <String>["birthday", "gender"], true, this );

With this request we are asking for user’s birthday and gender, we are telling the ANE to force the request (otherwise it could return a cached ExtendedUserProfile from previous request) and using this as a listener object that implements IAIRFacebookExtendedUserProfileListener. The callback methods are as follows:

public function onFacebookExtendedUserProfileSuccess( user:ExtendedUserProfile ):void {
    trace( "Extended user profile loaded" );

    trace( "Birthday:", user.getProperty( "birthday" ) );
    trace( "Gender:", user.getProperty( "gender" ) );
}

public function onFacebookExtendedUserProfileError( errorMessage:String ):void {
    trace( "Extended user profile error: " + errorMessage );
}
Important thing to remember is that only those properties we asked for in the original request will be available in the returned profile.

Updating user’s status

Updating user’s status is simply a POST request to Facebook’s Open Graph. Let’s see how we can use the /feed node of our user to post a message to his feed.

Since we want to publish content on user’s behalf, we need to be granted publish_actions permission – we can ask for it using:

AIRFacebook.loginWithPublishPermissions( new <String>["publish_actions"], this );

Assuming user has granted the permission to our app, we’re ready to make POST requests to the Open Graph. The ANE exposes three different methods which work directly with Open Graph:

  1. AIRFacebook.sendOpenGraphGETRequest()
  2. AIRFacebook.sendOpenGraphPOSTRequest()
  3. AIRFacebook.sendOpenGraphDELETERequest()

Since our intention is to post new content, we’ll use the second option. The method’s first parameter is an Open Graph path where the content should be created, or in some cases updated. By having our user logged in we’re in possesion of his access token, thus we can use the /me node followed by /feed. This gives us the Open Graph path where we want to post the content. Next, we must provide at least one of the required fields for this node. We’ll use the field message and place to post a message from Seattle, Washington. The full request then looks like this:

AIRFacebook.sendOpenGraphPOSTRequest(
	"/me/feed",
	{ message: "Hello world from Seattle.", place: "110843418940484" },
	this
);

Last parameter is object implementing IAIRFacebookOpenGraphListener interface that defines the following methods:

public function onFacebookOpenGraphSuccess( jsonResponse:Object, rawResponse:String ):void {
    trace( "Open Graph request parsed JSON:" );
    for( var key:String in jsonResponse ) {
        trace( "\t" + key + "->" + jsonResponse[key] );
    }
}

public function onFacebookOpenGraphError( errorMessage:String ):void {
    trace( "Open Graph request error: " + errorMessage );
}

The success callback gives us response in the form of a parsed JSON (where you will usually find an ID of the newly created post) as well as raw data returned by the native SDK.

Getting user’s friends

Facebook have changed its friend list policy in the last few years and we can no longer read user’s entire friend list. We can only retrieve those friends who have also used (i.e. logged in) our app. Furthermore, if any friend denied the user_friends permission during his login, that friend won’t appear in our retrieved list of friends. Let’s see how we can get at least those who like to use our app!

Essentially, getting user’s friends is just another Open Graph request, only this time we can make use of a helper method AIRFacebook.requestUserFriends() that also parses the response for us so that we can focus on the more important stuff. The method accepts fields parameter which is a list of properties that we want to retrieve from our user’s friends.

AIRFacebook.requestUserFriends( new <String>["id", "name", "gender"], this );

Last parameter is object implementing IAIRFacebookUserFriendsListener interface that defines the following methods:

public function onFacebookUserFriendsSuccess( friends:Vector.<ExtendedUserProfile> ):void {
    trace( "User friends loaded" );

    const length:uint = friends.length;
    for( var i:uint = 0; i < length; i++ ) {
    	const friend:ExtendedUserProfile = friends[i];
	trace( "Friend", friend.id, friend.name, friend.getProperty( "gender" ) );
    }
}

public function onFacebookUserFriendsError( errorMessage:String ):void {
    trace( "User friends request error: " + errorMessage );
}
Again, as in the case of requesting extended profile for logged in user, only those properties that were specified in the request will be available for us to read, provided the friend granted us the corresponding permission.

Sharing content

In addition to the POST request method that is useful for creating and updating content anywhere on the Open Graph, there are 5 helper methods for sharing specific content:

  • AIRFacebook.shareLink()
  • AIRFacebook.sharePhoto()
  • AIRFacebook.shareLinkMessage()
  • AIRFacebook.sharePhotoMessage()
  • AIRFacebook.shareOpenGraphStory()

Methods with the Message suffix are great for sharing content directly to a friend using the Facebook’s Messenger app. Actual availability of all these methods is subject to device’s capabilities and if Facebook or Messenger app is installed. We can use one of the following properties to check if the current device is able to share the corresponding content:

  • AIRFacebook.canShareLink
  • AIRFacebook.canSharePhoto
  • AIRFacebook.canShareLinkMessage
  • AIRFacebook.canSharePhotoMessage
  • AIRFacebook.canShareOpenGraphStory

Open Graph Story

Now, let’s assume we’ve made an amazing role-playing game where players can craft various items, like weapons, armors and magic potions. When player crafts an item we’d like him to be able to share his accomplishment, in the form of an Open Graph story. Open Graph story is a combination of Action (craft) and Object (weapon, armor…). Sharing a custom content similar to this can lead to an increased engagement and audience.

To create a custom story for our game we need to define action and object. We can do that by going to our Facebook app and selecting Open Graph from the menu on the left. We click the + Add Custom Story button in the middle of our screen. Note we could create our action and object separately but it’s quicker to do it in the Create a story dialog that is presented to us. We type our action’s name into the left input, our object’s name into the field on the right and hit the plus signs.

We continue by selecting our newly created action and object and click Create to create our story.

We’re presented with a page where we can edit our story name, tenses, default titles for variety of scenarios and so on. Let’s say we’re happy with the defaults for now. The story is now ready to be shared with the world!

It’s also possible to add custom properties to the object we’ve created. This is useful to further customize the story so for example our weapon could have rarity or damage properties.

Before we jump into code, we need to set namespace for our Facebook app by going to the Settings page that is accessible using the menu on the left. Now we’re ready to share our story using the following:

AIRFacebook.shareOpenGraphStory(
	"app_namespace:craft",
	"app_namespace:weapon",
	"Sword of Light",
	"http://img11.deviantart.net/5a29/i/2005/337/2/7/sword_by_sammy65.jpg",
	null, // No custom properties for the weapon object
	this
);

Last parameter is a listener object implementing IAIRFacebookShareListener:

public function onFacebookShareSuccess( postID:String ):void {
    trace( "Share success, post id: " + postID );
}

public function onFacebookShareCancel():void {
    trace( "Share cancelled" );
}

public function onFacebookShareError( errorMessage:String ):void {
    trace( "Share error: " + errorMessage );
}

Sword image created by Sammy65.

If our weapon object had a custom property called rarity we could specify it in the code like this:

AIRFacebook.shareOpenGraphStory(
	"app_namespace:craft",
	"app_namespace:weapon",
	"Sword of Light",
	"http://img11.deviantart.net/5a29/i/2005/337/2/7/sword_by_sammy65.jpg",
	{ "app_namespace:rarity": "LEGENDARY" }, // custom weapon property
	this
);
It’s necessary to specify app’s namespace when referring to Actions, Objects and their properties.

It’s worth noting that apps using Open Graph stories need to be reviewed by Facebook, see Facebook docs for more information about submission for review.

Demo application

To see more examples of AIRFacebook’s API take a look at the source codes for a demo app on GitHub.