Abstract

The Force.com Toolkit for Facebook allows your Force.com apps to manipulate the Facebook Graph API. The toolkit provides a set of Apex classes, such as FacebookUser and FacebookPost, that model Facebook Graph API Objects such as User and Post.

This article shows you how to

  • install the toolkit
  • configure the included sample Facebook app
  • get started writing your own social application

There is a sample deployment of the toolkit online - you can visit this Force.com Site, login via Facebook, and see the toolkit in action:

FacebookToolkitSampleSite.png

All the code behind the sample site is included in the toolkit and described below.

Major changes in Version 3

  • There is a new custom object, FacebookSession__c, that associates the Facebook access token with a session cookie. This allows a Force.com Site to authenticate users via Facebook.
  • The toolkit uses the new native JSON implementation, mitigating issues in earlier versions where JSON was parsed in an Apex utility class, which severely limited the amount of data that could be parsed.
  • After the Spring '12 Salesforce release, the toolkit will be updated to support the new Facebook Authentication Provider functionality.

Installation

There are two mechanisms for installing the toolkit: as an unmanaged package, or from GitHub. Choose the unmanaged package if you will be using the toolkit to develop your own Facebook app. If you are considering modifying or extending the toolkit itself, then installing from GitHub is a little more work, but will enable you to more easily contribute code back to the project.

Installing the Unmanaged Package

  1. Create a new Developer Edition (DE) account at http://developer.force.com/join. You will receive an activation email - click the enclosed link to complete setup of your DE environment. This will also log you in to your new DE environment.
  2. Install the unmanaged package into your new DE org via this URL: https://login.salesforce.com/packaging/installPackage.apexp?p0=04td00000001Hsg
    FacebookToolkitPackage.png
  3. Click through the screens to complete installation.

Installing from GitHub

  1. Create a new Developer Edition (DE) account at http://developer.force.com/join. You will receive an activation email - click the enclosed link to complete setup of your DE environment. This will also log you in to your new DE environment.
  2. Create a new Force.com project in the Force.com IDE using your new org's credentials. In the 'Choose Initial Project Contents' dialog, select 'Selected metadata components', hit 'Choose...' and select ALL of the components in the next page. This will give you a complete project directory tree.
  3. Clone the GitHub project into the Force.com IDE project directory. You will need to clone it first to a temporary location, since git will not let you clone to a directory with existing content:
$ git clone --no-checkout \
    git://github.com/developerforce/Force.com-Toolkit-for-Facebook.git \
    /path/to/your/projectdir/tmp
$ mv /path/to/your/projectdir/tmp/.git /path/to/your/projectdir
$ rm -rf /path/to/your/projectdir/tmp
$ cd /path/to/your/projectdir
$ git reset --hard HEAD
  1. In Eclipse, right click your project in the project explorer and click 'Refresh'. This causes Eclipse to scan the project directory tree for changes, and the plugin syncs changes to Force.com.
  2. In your DE environment, go to Setup | App Setup | Create | Apps, click 'Edit' next to the Facebook Toolkit 3 app, scroll down, click the 'Visible' box next to System Administrator and hit 'Save'. Now go to Setup | Administration Setup | Manage Users | Profiles, click on 'Edit' next to System Administrator, scroll down to Custom Tab Settings, set 'Facebook Apps', 'Facebook Social Samples', 'Facebook Sessions' and 'Facebook User Connections Test' to 'Default On' and hit 'Save'. 'Facebook Toolkit 3' should now be available in the dropdown list of apps (top right).
  3. Go to Setup | Administration Setup | Security Controls | Remote Site Settings and add https://graph.facebook.com as a new remote site.
    FacebookToolkitRemoteSite.png

Configuring the Sample Force.com Site

  1. Go to Setup | App Setup | Develop | Sites and create a new site. Set the home page to FacebookSamplePage and add FacebookTestUser to the list of Site Visualforce Pages. Ensure you activate the site.
  2. Go to Setup | App Setup | Develop | Apex Classes, hit the 'Compile All Classes' link, then click 'Schedule Apex' and add FacebookHousekeeping - set it to run at midnight every night. This scheduled Apex job will remove expired session records from the FacebookSession__c object.
  3. Go to the Facebook Apps Page, click 'Create New App' and complete the required fields. Under 'Website', set Site URL to your site's secure URL - for example, https://fbtest-developer-edition.na14.force.com/
    FacebookToolkitFacebookApp.png
  4. In your DE environment, select the 'Facebook Toolkit 3' app from the application menu at top right, then click the 'Facebook Apps' tab. Create a new Facebook app, copying 'App ID' and 'App Secret' from your new app's settings in Facebook. Set 'Extended Permissions' to a comma-separated list of permissions to allow the sample app to access more data; for example, you might use read_stream, publish_stream to allow the app to read and write posts on the user's feed. See the Facebook Graph API documentation for a full discussion of permissions.
    FacebookToolkitFacebookApp2.png
  5. Go to your site URL (e.g. https://fbtest-developer-edition.na14.force.com/) and you should be prompted to log in to your new app and authorize it to access your data:
    FacebookToolkitAuthorization.png
    Click Install and you should see a sample page showing your Facebook user name, profile picture, feed, 'Like' button etc. There are also buttons to dynamically retrieve your user profile and friends list:
    FacebookToolkitUserInfo.png
  6. Now you have the sample page working, you have a starting point for a Facebook app running on Force.com. Examine FacebookSamplePage and FacebookSampleController to see how the sample app is put together.

Developing a Facebook App with the Toolkit

Every Facebook Graph API call must be accompanied by an access token; the access token authorizes your app to access the Graph API on behalf of the authenticated user. Facebook uses the OAuth 2.0 protocol for authentication and authorization. Your app must send users to Facebook to log in and authorize your app to access the Graph API on the users' behalf. There are two ways of doing this, depending on whether you want to map Facebook users to identities in salesforce.com.

No Mapping

You can implement your app as a Force.com Site, in which, as far as the Force.com platform is concerned, all users are mapped to a single Site Guest user. You must manage any user-related data yourself, typically indexed by users' Facebook IDs, and you must use the FacebookLoginController supplied with the toolkit as a base class for your app's controllers. FacebookLoginController manages the OAuth 2.0 interaction with Facebook, randomly generates a session cookie for the user, and maintains a FacebookSession custom object mapping session cookies to Facebook access tokens. Your Visualforce pages must set their action attribute to the controller's login method so that FacebookLoginController can obtain the access token:

   <apex:page controller="FacebookSampleController" action="{!login}" 
     cache="false" sidebar="false" showHeader="false" 
     title="Force.com Toolkit for Facebook - Sample Page">

Your controller code can now retrieve the current user's token with FacebookToken.getAccessToken().

Map Facebook Accounts to Salesforce Users

Alternatively, from Spring '12 onwards, you can implement your app within a Salesforce org or portal. In this case, each Facebook account is mapped to a unique user within your Salesforce org. Social Single Sign-On – Authentication Providers in Spring ’12 gives an overview of configuring Facebook as an Authentication Provider and linking existing salesforce.com users' accounts to their Facebook accounts, or creating new accounts for users arriving from Facebook.

If you are using the Facebook Authentication Provider, you need not use FacebookLoginController; the platform will manage interaction with Facebook for you. Your Apex code can retrieve the current user's token with Auth.AuthToken.getAccessToken(AuthProviderID, AuthProviderType);.

Since the main intent of this first, Spring '12, release of Authentication Provider functionality is to provide single sign-on and account linking, there are some limitations in using the FB access token with the Graph API:

  • The Facebook Authentication Provider requests only the email permission, limiting the amount of data you can retrieve via the Graph API to the user's email address, user id, name, profile picture, gender, age range, locale, networks, list of friends, and any other information they have made public. It is expected that developers will be able to set a custom set of requested permissions in a future release.
  • The Facebook access token will expire after two hours. There is currently no mechanism for obtaining a fresh access token. One possible strategy for handling this issue would be to detect token expiry and offer to redirect the user to the Authentication Provider SSO link to reauthenticate to Salesforce.

Accessing the Graph API

However you obtain the access token, accessing the API follows the same pattern. You can retrieve most Facebook Graph API objects by calling the relevant constructor with the access token and an id (for example, me) or connection (for example, me/friends) and an optional map of API parameters.

So, to retrieve the User object for a user with Facebook ID 1111111111:

   FacebookUser user = new FacebookUser(access_token, '1111111111');
   

and to retrieve a list of friends, including their hometowns, for the currently authenticated user:

   Map<String,String> params = new Map<string,string>{'fields' => 'id,name,hometown'};
   FacebookUsers friends = new FacebookUsers(access_token, 'me/friends', params);

Note that your app is limited to the data to which the authenticated user and other users have granted access.

Once your app has retrieved a Graph API object, it can manipulate it in Apex or Visualforce using its Apex properties. Here a Visualforce page iterated through the friends object obtained above:

   <apex:pageBlockTable value="{!friends.data}" var="friend">
       <apex:column value="{!friend.id}" headerValue="Id"/> 
       <apex:column value="{!friend.name}" headerValue="Name"/>
       <apex:column value="{!friend.hometown.name}" headerValue="Hometown"/>
   </apex:pageBlockTable>

You can see many similar examples in the sample pages and controllers:

  • FacebookSamplePage
  • FacebookSampleController
  • FacebookTestUser
  • FacebookTestUserController

Writing a Simple Facebook Application

Let's write a simple Facebook app on Force.com that uses the toolkit to show users information on their friends. Many Facebook apps are games or quizzes that let uses see their friends scores, but, to keep things simple, we'll simply ask users to enter their favorite number, and show them a list of their friends, ranked by their favorite numbers.

First, we'll create a custom object in Force.com to hold our users' Facebook IDs, names, and favorite numbers.

  1. Go to Setup | App Setup | Create | Objects and click New Custom Object.
  2. Give it the label Facebook Favorite, plural Facebook Favorites, and change the record name to Facebook Name, leaving the data type as Text.
  3. Click Save, then click New in the Custom Fields & Relationships section.
  4. Click Text then Next.
  5. Enter a Field Label of Facebook Name, with a length of 100 and click Required and Unique - every record must have a unique Facebook ID.
  6. Click Next, then Next, then Save & New, accepting the defaults.
  7. Click Number then Next.
  8. Enter a Field Label of Favorite Number and click Required - every record must have a Favorite Number.
  9. Click Next, then Next, then Save, accepting the defaults.

Now we have a simple data model, we'll implement a Visualforce page and controller that will allow users to enter their favorite number and see their friends' favorites.

The following steps use the declarative browser interface; you can use the Force.com IDE if you prefer.

  1. Go to Setup | App Setup | Develop | Apex Classes and click New.
  2. Paste in the following code and click Save:
global with sharing class FacebookFavoriteController 
extends FacebookLoginController {
    public static String getAccessToken() {
        return FacebookToken.getAccessToken();
    }

    public FacebookUser me {
        get {
            // Can't set up 'me' in the controller constructor, since the 
            // superclass 'login' method won't have been called!
            if (me == null) {
                String accessToken = getAccessToken();

                // If accessToken is null, it's likely that the page's action 
                // method has not yet been called, so we haven't been to FB to
                // get an access token yet. If this is the case, we can just 
                // leave 'me' as null, since the redirect will happen before
                // HTML is sent back.
                if (accessToken != null) {
                    me = new FacebookUser(accessToken, 'me');                    
                }
            }

            return me;
        } set;
    }

    public Integer favorite {
        get {
            if (favorite == null) {
                String accessToken = getAccessToken();

                if (accessToken != null) {
                    List<Facebook_Favorite__c> favorites = 
                        [SELECT Favorite_Number__c
                            FROM Facebook_Favorite__c
                            WHERE Facebook_ID__c = :me.id];

                    favorite = (favorites.size() == 0) ? 
                        null : 
                        Integer.valueOf(favorites[0].Favorite_Number__c);
                }
            }

            return favorite;
        } set;
    }

    private FacebookUsers friends { 
        get {
            if (friends == null) {
                String accessToken = getAccessToken();

                if (accessToken != null) {
                    Map<String, String> params = 
                        new Map<String, String>{'fields' => 'id,name'};
                    friends = new FacebookUsers(accessToken, 'me/friends', params);
                }
            }

            return friends;
        } set;
    }

    public List<Facebook_Favorite__c> friendsFavorites {
        get {
            if (friendsFavorites == null) {
                String accessToken = getAccessToken();

                if (accessToken != null) {
                    List<String> friendIDs = new List<String>();

                    // Show my own favorite number, if I have one
                    friendIDs.add(me.id);    

                    for (FacebookUser friend : friends.data) {
                        friendIDs.add(friend.id);
                    }

                    friendsFavorites = [SELECT Name, Facebook_ID__c, Favorite_Number__c
                        FROM Facebook_Favorite__c
                        WHERE Facebook_ID__c IN :friendIDs
                        ORDER BY Favorite_Number__c DESC];
                }
            }

            return friendsFavorites;
        } set;
    }

    public PageReference save(){
        if (me == null) {
            throw new FacebookException('Cannot save without a Facebook user!');
        }

        List<Facebook_Favorite__c> favorites = 
            [SELECT Favorite_Number__c
                FROM Facebook_Favorite__c
                WHERE Facebook_ID__c = :me.id];

        if (favorites.size() == 0) {
            Facebook_Favorite__c newFavorite = new Facebook_Favorite__c(Name = me.name, 
                Facebook_ID__c = me.id, 
                Favorite_Number__c = favorite);
            insert newFavorite;
        } else {
            favorites[0].Favorite_Number__c = favorite;
            update favorites[0];
        }

        // Force the favorites list to be refreshed so I see my own favorite
        friendsFavorites = null;

        return null;
    }

    public PageReference refreshFriendsFavorites() {
        // Force the friends list to be refreshed
        friends = null;

        // Force the favorites list to be refreshed
        friendsFavorites = null;

        return null;
    }    
}

Note the pattern established in the controller for initializing the me, favorite, friends and friendsFavorites properties. We cannot initialize the properties in the controller's constructor, since it will be executed before the user has a chance to login to Facebook. Each property implements its get accessor to only initialize the property if an access token is available. You should follow a similar pattern in your own controllers.

Notice in particular the get accessor for the friendsFavorites property - this is where data in Force.com is combined with Facebook graph data, making this a social application. The user's friends list is retrieved from Facebook and used to filter the SELECT query to retrieve only the user's friends' favorite numbers, in descending order of favorite number. This is how a typical social game dynamically constructs its your friends' high scores table.

Now create a Visualforce Page for your application:

  1. Go to Setup | App Setup | Develop | Pages and click New.
  2. Name your page FacebookFavoritePage, paste in the following code, and click Save:
<apex:page controller="FacebookFavoriteController" action="{!login}" 
  cache="false" sidebar="false" title="Facebook Favorite Number">
    <div id='fb-root'></div>
    <script src='https://connect.facebook.net/en_US/all.js' />
    <apex:pageBlock title="Welcome, {!me.name}!">
        <!-- Collect a favorite number from the user -->
        <apex:form >
            <apex:outputLabel >Enter your favorite number:</apex:outputLabel>
            <apex:inputText value="{!favorite}"/>
            <apex:commandButton value="Submit" action="{!save}"/>
        </apex:form>
        <br/>
        <!-- Only show the 'Share' button if the user has entered a favorite -->
        <apex:outputPanel rendered="{!NOT(ISBLANK(favorite))}">
            <input type="submit" class="btn" 
                onclick="postToFeed(); return false;" 
                value="Share with Friends"/>
            <p id='msg'></p>
            <script> 
              FB.init({appId: '{!appId}', status: true, cookie: true});

              function postToFeed() {
                var obj = {
                  method: 'feed',
                  link: '{!$CurrentPage.URL}',
                  picture: 'http://www.developerforce.com/assets/developerforcesite/images/forcebug.png',
                  name: 'Force.com Favorite Number Facebook App',
                  caption: 'A Demonstration of the Force.com Toolkit for Facebook',
                  description: 'My favorite number is {!favorite}! What\'s yours?'
                };

                function callback(response) {
                    if (response['post_id']) {
                        var postId = response['post_id'].split('_')[1];
                        document.getElementById('msg').innerHTML = 
                            "Posted to your wall. "+
                            "<a href=\"https://www.facebook.com/permalink.php?"+
                            "id={!me.id}&v=wall&story_fbid="+postId+"\">View your post</a>";
                    }
                }

                FB.ui(obj, callback);
              }
            </script>
        </apex:outputPanel>
        <br />
        <apex:pageBlockSection title="Your Friends' Favorite Numbers" 
            columns="2" id="friendsFaves">
            <apex:pageBlockSectionItem >
                <apex:outputPanel >
                    <apex:pageBlockTable value="{!friendsFavorites}" var="fave">
                        <apex:column headerValue="Name">
                            <!-- Link to each friend's Facebook page -->
                            <apex:outputLink target="_new" 
                                value="https://www.facebook.com/{!fave.Facebook_ID__c}">
                                {!fave.Name}
                            </apex:outputLink>
                        </apex:column>
                        <apex:column value="{!fave.Favorite_Number__c}" 
                            headerValue="Favorite Number"/>
                    </apex:pageBlockTable>
                    <br/>
                    <apex:actionStatus startText="Updating List" 
                        stopText="Click Refresh to update friends list" 
                        id="friendsStatus"/>
                </apex:outputPanel>
            </apex:pageBlockSectionItem>
            <apex:pageBlockSectionItem >
                <!-- Blank section item so Refresh button is rendered below list -->
            </apex:pageBlockSectionItem>
            <apex:pageBlockSectionItem >
                <apex:form >
                    <apex:commandButton value="Refresh" 
                        action="{!refreshFriendsFavorites}" 
                        rerender="friendsFaves" status="friendsStatus"/>
                </apex:form>
            </apex:pageBlockSectionItem>
            </apex:pageBlockSection>
    </apex:pageBlock>
</apex:page>

The Visualforce Page is fairly straightforward - it displays the user's name via the me property, allows the user to enter their favorite number, allows the user to post their favorite number to their wall, and shows the list of friends' favorites as a table. There is a button to allow the user to refresh the list as friends add and change their favorite numbers.

The postToFeed() JavaScript function illustrates the use of a Facebook Feed Dialog to post content to the user's wall. Notice the use of Visualforce merge fields to embed the application ID, current page URL and current user ID in the JavaScript code. On a successful post, a message is displayed with a link to the post.

You will need to create a Facebook app and Force.com Site to make your app accessible; follow the instructions above if you have not already done so, making FacebookFavoritePage the home page for the site.

You will also need to give the Site 'Guest' user access to the custom object:

  1. Go to Setup | App Setup | Develop | Sites and click your site label.
  2. Click Public Access Settings and click Edit
  3. Scroll down to Custom Object Permissions and check Read, Create and Edit for Facebook Favorites.

Now you should be able to go to your site URL, login to the Facebook app, and see the Favorite Number app:

FacebookFavoriteNumberSite.png

Enter your favorite number and click Submit. You will see your favorite number appear in the list. Have your Facebook friends visit your Site and enter their favorite numbers and the list should grow.

Click the button to share your favorite number with your friends and you should see a dialog appear:

FacebookToolkitDialog.png

Enter a message, click Share and check your Facebook profile to see the new post.

Conclusion

Version 3.0 of the Force.com Toolkit for Facebook allows you to write applications that authenticate and authorize users via Facebook, manipulate the Facebook Graph API, and leverage the power of the Force.com platform. Now it's up to you to combine your business data in Force.com with your users' social data in Facebook to create your own new Social Applications!

About the Author

Pat Patterson is a member of the Developer Evangelism team at salesforce.com, currently focusing on integration and dynamic languages. Describing himself as an 'articulate techie', Pat hacks all manner of code from Node.js web apps down to Linux kernel drivers, writing it all up on the Force.com blog, his own personal blog Superpatterns, and, of course, his Twitter feed.