Skip to main content

How to integrate Facebook's JavaScript SDK with GWT

First of all - you need to have a Facebook account. Then you need to create an application on Facebook at http://www.facebook.com/developers/createapp.php, that will give you an App Id. This id is tightly connected to the url/site from which you will be using the JavaScript SDK and you'll be using it later.

For development, localhost addresses works fine - i.e. http://127.0.0.1:8888.

Next thing is to add the actual JavaScript library to you hostpage, which according to Facebook means adding this piece of JavaScript just before the body-endtag:

<!-- Facebook integration -->
<div id="fb-root"></div>
<script>
(function() {
var e = document.createElement('script'); e.async = true;
e.src = document.location.protocol +
'//connect.facebook.net/en_US/all.js';
document.getElementById('fb-root').appendChild(e);
}());
</script>

It will dynamically add a script-tag into your html-file, which bypasses the "Same Origin Policy" all together. (No need for a xd_receiver.htm, as with previous versions of the API, that is).

Now it's time to initialize the Facebook API using native methods in GWT:

private native String initFacebookAPI()
/*-{
$wnd.FB.init({appId: '<YOUR_APP_ID_HERE>', status: true, cookie: true, xfbml: true});
$wnd.FB.Event.subscribe('auth.sessionChange', function(response) {
if (response.session) {
// A user has logged in, and a new cookie has been saved
$wnd.onLogin();
} else {
// The user has logged out, and the cookie has been cleared
$wnd.onLogout();
}          
});
}-*/;

$wnd.FB works because we dynamically included the Facebook .js-file in the previous step. So, anything you'd want to do when initializing the API can be done here, e.g. adding subscriptions to events (as above).

As can be seen, two different methods are being called when a sessionChange-event occurs (depending on what happened). These methods will be regular GWT-methods, but we need to export them into the DOM in order to call them from within native code:

private native void exportMethods(Application instance) /*-{
$wnd.onLogin = function() {
return instance.@com.technowobble.gwt.client.Application::onLogin()();
}
$wnd.onLogout = function() {
return instance.@com.technowobble.gwt.client.Application::onLogout()();
}
$wnd.onAPICall = function(callback, response, exception) {
return instance.@com.technowobble.gwt.client.Application::onAPICall

(Lcom/google/gwt/user/client/rpc/AsyncCallback;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)

(callback, response, exception);
}
}-*/;

There's also on more function being exported (which needs some explanation...) It serves as a callback function for a native method exposing the Facebook Graph API:

private native void callAPI(String path, AsyncCallback<JavaScriptObject> callback) /*-{
$wnd.FB.api(path, function(response) {
if (!response) {
alert('Error occured');
} else if (response.error) {
alert($wnd.dump(response));
// call callback with the actual error
$wnd.onAPICall(callback, null, response.error);
} else if (response.data) {
alert($wnd.dump(response));
// call callback with the actual json-array
$wnd.onAPICall(callback, response.data, null);
} else {
alert($wnd.dump(response));
// call callback with the actual json-object
$wnd.onAPICall(callback, response, null);
} 
});
}-*/;

What happens here is that any GWT-method now can make calls like this:

callAPI("/me", new AsyncCallback<JavaScriptObject>() {

@Override
public void onFailure(Throwable caught) {
Window.alert(caught.getMessage());
}

@Override
public void onSuccess(JavaScriptObject result) {
UserJso fbUser = (UserJso) result;
Window.alert(fbUser.getFullName());
}
});   

The onAPICall exported previously is the method binding the first call to the AsyncCallback:

public void onAPICall(AsyncCallback<JavaScriptObject> callback,
JavaScriptObject response, JavaScriptObject exception) {
if (response != null) {
callback.onSuccess(response);
} else {
ExceptionJso e = (ExceptionJso) exception;
callback.onFailure(new Exception(e.getType() + ": " + e.getMessage()));
}
}

Note that the AsyncCallback used for a specific Graph API call, e.g. "/me" is free to cast the result to an overlay type (the result should be a json-representation). The UserJso looks like this:

package com.technowobble.gwt.client.json;

import com.google.gwt.core.client.JavaScriptObject;

public class UserJso extends JavaScriptObject {
// Overlay types always have protected, zero-arg constructors
protected UserJso() { }

public final native String getFirstName() /*-{ return this.first_name; }-*/;
public final native String getLastName()  /*-{ return this.last_name;  }-*/;

public final String getFullName() {
return getFirstName() + " " + getLastName();
}
}

A full example project can be found here and should get you started on more elaborative usage.

Comments

  1. Great use of GWT =)
    Only challenge I had with your example was the lack of a "wait until $wnd.FB !== undefined" before any FB calls.
    Thanks for sharing!

    ReplyDelete
  2. Thank you very much, Mattias.
    This article is very helpful for me.

    I am using Smartgwt. I modified a little then the codes can work.

    Still the same problem as George said, "undefined".

    I tried to use " if (typeof($wnd.FB)== undefined) " or "try{}catch(e){}" before "$wnd.FB.api" call, but it doesn't work.

    Can you give me a little tips how to do this?
    I am a newbie of Javascript.

    ReplyDelete
  3. Hi,

    I've actually never had any problems with the FB-object not being ready when I've accessed it...

    I guess one viable option would be add the FB-api in a non-async way, or listen to some callback function that would update some "FBReady" variable that you could check before using the object?

    Let's ask other readers to collaborate to come up with a working example, shall we?

    ReplyDelete
  4. I also have the problem with $wnd.FB == undefined. It is incrediibly annoying because sometimes it works and sometimes (especially when I need it) it does not. I am completely stuck here. Did anyone come up with a solution yet?
    Thanks!!

    Malte

    ReplyDelete
  5. Excellent Post, it was very helpful and simple.
    Regarding the problem of the time it takes for the FB object to be loaded, you can use the suggestion for Asynchronous Loading from FB site: http://developers.facebook.com/docs/reference/javascript/fb.init/

    In general I moved the code for initialization of the FB object from the GWT code to the HTML file


    ...
    window.fbAsyncInit = function() {
    FB.init({appId: 'YOUR-APP-ID', status: true, cookie: true,
    xfbml: true});
    FB.Event.subscribe('auth.sessionChange', function(response) {
    if (response.session) {
    // A user has logged in, and a new cookie has been saved
    onLogin();
    } else {
    // The user has logged out, and the cookie has been cleared
    onLogout();
    }         
    });
    };
    (function() {
    var e = document.createElement('script');
    e.async = true;
    e.src = document.location.protocol
    + '//connect.facebook.net/en_US/all.js';
    document.getElementById('fb-root').appendChild(e);
    }());
    ...

    ReplyDelete
  6. Great post. I was in the process of figuring this out and it saved me lots of time.

    I believe I fixed the "undefined" problem in my code by exporting my GWT based init code to "$wnd.fbAsyncinit"...the callback FB will look for. That way the FB code calls your init code itself once its loaded instead of you trying to time it. Then all you have to do is call your "export" method early on so its there before FB is loaded. Its basically the same as what Guy has posted above but lets you still write Java if you wish.

    ReplyDelete
  7. I agree with Phil that doing it in GWT code is exactly the same if you prefer it over plain javascript.

    Another issue that I faced was the case that the user is already logged in into facebook and the page/application. I didn't receive the 'auth.sessionChange' event to trigger the onLogin method.

    Therefore, I added a call to getLoginStatus right after the init, with the same onLogin and onLogout calls:


    FB.getLoginStatus(function(response) {
    if (response.session) {
    // A user has logged in, and a new cookie has been saved
    onLogin();
    } else {
    // The user has logged out, and the cookie has been cleared
    onLogout();
    }         
    });

    ReplyDelete
  8. check this out, properly working:
    http://code.google.com/p/gwt-gae-fb/

    ReplyDelete
  9. Positive site, where did u come up with the information on this posting? I'm pleased I discovered it though, ill be checking back soon to find out what additional posts you include. Buy Negative Facebook Reviews

    ReplyDelete
  10. buy5stareviews
    The information you have posted is very useful. The sites you have referred was good. Thanks for sharing this: Buy Negative Facebook Reviews

    ReplyDelete

Post a Comment

Popular posts from this blog

GWT and Spring Security

Update! - Based on the post below, and my other post regarding Spring Security and OpenID, I have added Open-ID support to the sample application below. For those interested, here's the write-up of changes. I've spent quite some time digging into ways of integrating GWT and Spring Security. It all started by reading the following post in the GWT Forum - Best practices/ideas for GWT with Spring Security (or equivalent) , and then checking out this blog - GWT and Spring Security . To make matters worse, I started reading Security for GWT Applications and specifically about the "Cross-Site Request Forging"-attacks. Now, what could I do about it? Well, starting by setting up my own project (Maven-based) with all updated dependencies (GWT 2.0.3 etc) and started reading the Spring Security Reference Documentation (puh!). Instead of See Wah Cheng's approach of implementing a custom authentication service, I decided to rely on standard namespace configuration

Loading Google Maps API asynchronously with RequireJS

With Single Page Web Applications becoming more and more popular, I decided to understand the concepts of various Javascript frameworks a little better. There are literally hundreds of them, but I decided to start with a really nice tutorial written by Alex Young. Cornerstones in this tutorial are BackboneJS, Underscore, Bootstrap and RequireJS. After been through the tutorial I decided to roll my own project based on the same setup. I wanted to use Google Maps for this, and searched for a way to load the API using RequireJS. Turned out that there are a few different approaches, but the most common seems to be to use the async-plugin created by Miller Medeiros. Jason Wyatt has another interesting solution which caught my attention. Being new to all this, I really didn't feel like start involving plug-ins from remote repositories. It might be the most natural thing to do, but one step at a time is more my melody. Jason's solution had some drawbacks mentioned in the comm

Google Apps Script and ES Modules

Currently, Google Apps Script does not support ES modules - and any usage of export/import will fail. One way of handling this is to use rollup.js to bundle your project into one single JavaScript file. The trick here is to make sure not to export any functions in your entry point code, e.g. index.ts , and to prevent any generation of export statement in the final bundle (see the custom rollup plugin in the rollup.config.js below). import { babel } from "@rollup/plugin-babel"; import { nodeResolve } from "@rollup/plugin-node-resolve"; const extensions = [".ts", ".js"]; const preventThreeShakingPlugin = () => { return { name: 'no-threeshaking', resolveId(id, importer) { if (!importer) { // let's not theeshake entry points, as we're not exporting anything in Apps Script files return {id, moduleSideEffects: "no-treeshake" } } return null; }