Using the Unity Facebook SDK with iOS and Android

It’s great that Facebook have supplied an SDK which allows Unity developers to integrate sign in into our mobile games. However, I found that the documentation is sometimes out of date, and the example code is not always that easy to understand. There’s also a couple of “gotchas” which are good to know about!

I thought it might be useful to share my method of handling Facebook sign in. I’ve snipped some bits of code out in this overview. The complete script is available here. You also need the FacebookUtil class, taken from the FriendSmash example.

Prerequisites

  • Requires Unity 4.6+ to make use of Unity Events.
  • Create your app on Facebook.
  • Import the Facebook Unity SDK into your Unity project. I am using version 6.1 (beta).
  • Try building and running the game on a device. Leave a comment if you get stuck trying to sort out any errors!

Storing user info

I use a simple class to hold Facebook user information. Only basic info has been included, which is available from user’s public profile. If you need more user info, you may need to request additional permissions. The user can choose to reject the permission requests when they sign in, so you will need to handle those cases accordingly.

public class FacebookUserInfo {
    public string id = null;
    public string firstName = "Unknown";
    public string lastName = "";
    public string gender = "unknown";
    public Sprite profilePicture = null;
}

FacebookManager class variables and properties

We’re going to make use static methods and properties, so that other scripts can access stuff without needing a component reference. There’s quite a few variables and properties here, but they’re all very simple! The events and public properties allow other scripts to keep up to date on what the login status is, give access to user info, and what permissions have been granted.

public class FacebookManager : MonoBehaviour {
    private static FacebookManager instance = null;
    
    // you can assign event handler methods in the inspector
    public UnityEvent loginStatusChangedEvent;
    public UnityEvent userInfoDownloadedEvent;
    public UnityEvent friendsInfoDownloadedEvent;

    // user login state
    public enum LoginStatusCode {
        LoggingOut,
        NotLoggedIn,
        AttemptingFBLogin,
        LoggedIn,
        WaitingForConnection
    }
    
    private static LoginStatusCode loginStatus = LoginStatusCode.WaitingForConnection;
    
    public static LoginStatusCode LoginStatus {
        private set {
            loginStatus = value;
            if (instance != null) {
                // fire loginStatusChangedEvent
                instance.OnLoginStatusChanged();
            }
        }
        get {
            return loginStatus;
        }
    }
    
    private static FacebookUserInfo currentUserFacebookUserInfo = new FacebookUserInfo();
    public static FacebookUserInfo CurrentUserFacebookUserInfo {
        get { return currentUserFacebookUserInfo; }
        private set { currentUserFacebookUserInfo = value; }
    }
    
    private static List<FacebookUserInfo> friendUserFacebookInfos = new List<FacebookUserInfo>();
    public static List<FacebookUserInfo> FriendUserFacebookInfos {
        get { return friendUserFacebookInfos; }
        private set { friendUserFacebookInfos = value; }
    }
    
    // keep track of which permissions have been requested and granted 
    // in case other scripts require them
    private static Dictionary<string, bool> userFacebookPermissions = new Dictionary<string, bool>();
    
    // SNIPPED more permission properties. See full script.

    public static bool PublishPermissionRequested {
        get {
            return userFacebookPermissions.ContainsKey("publish_actions");
        }
        private set { ;}
    }

    private static bool Connected {
        get {
            return Application.internetReachability == NetworkReachability.ReachableViaLocalAreaNetwork 
                || Application.internetReachability == NetworkReachability.ReachableViaCarrierDataNetwork;
        }
        set { ;}
    }
}

Initialising Facebook

When you initialise Facebook, it will login automatically if the user logged in when they last played the game. However, this only works when there’s an internet connection. Facebook will not restore a login session unless you’re online. I use a Coroutine to wait for a internet connection to become available. It checks for a connection every 5 seconds, and then waits 10 seconds between initialisation attempts.

void Start() {
    #if UNITY_IPHONE || UNITY_ANDROID
    StartCoroutine(InitialiseFacebook());
    #else
    LoginStatus = LoginStatusCode.NotLoggedIn;
    #endif
}

IEnumerator InitialiseFacebook() {
    LoginStatus = LoginStatusCode.WaitingForConnection;
    // keep repeating until FB initialises
    while (!FB.IsInitialized) {
        if (Connected && LoginStatus != LoginStatusCode.AttemptingFBLogin) {
            LoginStatus = LoginStatusCode.AttemptingFBLogin;
            FB.Init(OnFBInitComplete, null, null);
            yield return StartCoroutine(WaitForSecondsUnscaled(10.0f));
        }
        else if (!Connected && LoginStatus != LoginStatusCode.WaitingForConnection) {
            LoginStatus = LoginStatusCode.WaitingForConnection;
        }
        else {
            yield return StartCoroutine(WaitForSecondsUnscaled(5.0f));
        }
    }
}

// use unscaled time so that it still works when game is paused
IEnumerator WaitForSecondsUnscaled(float seconds) {
    float startTime = Time.realtimeSinceStartup;
    while (Time.realtimeSinceStartup - startTime < seconds) {
        yield return null;
    }
}

void OnFBInitComplete() {
    if (FB.IsLoggedIn) {
        LoginStatus = LoginStatusCode.LoggedIn;
        GetFBUserInfo();
    }
    else {
        LoginStatus = LoginStatusCode.NotLoggedIn;
    }
}

Getting user info

We use the Facebook Graph API to retrieve data from Facebook using HTTP queries. The FB.API method sends the query, and returns the results as a FBResult object. We get the user permissions first, to see if we have access to their friends list. Here we’re using an anonymous function in place of a delegate.

void GetFBUserInfo() {
    // Use an anonymous function in place of delegate
    // It is like calling FB.API("...", Facebook.HttpMethod.GET, DelegateMethodName)
    FB.API("/me/permissions", Facebook.HttpMethod.GET, (FBResult result) => {
        if (result.Error != null) {
            Debug.LogError("Error getting FB permissions: " + result.Error);
        }
        else {
            // use Facebook methods supplied to deserialize the result
            // we don't need to know how they do it, just what data type is returned 🙂
            Dictionary<string, object> dataObjects = Facebook.MiniJSON.Json.Deserialize(result.Text) as Dictionary<string, object>;
            List<object> permissionObjects = dataObjects["data"] as List<object>;
            
            // lets store all the permission info in case we need to know what's been granted
            userFacebookPermissions = new Dictionary<string, bool>();
            
            foreach (object permissionObject in permissionObjects) {
                Dictionary<string, object> permissionObjectDict = permissionObject as Dictionary<string, object>;
                foreach (KeyValuePair<string, object> pair in permissionObjectDict) {
                    userFacebookPermissions.Add(pair.Key, System.Convert.ToBoolean(pair.Value));               
                }
            }
            
            // get the current user's info. 
            // /me graph URL always points to current user
            StartCoroutine(GetFacebookUserInfo("/me", true));
        }
    });
}

The method for getting a user’s info is rather long winded. Mainly due to a bug which means that we need to query the URL of the profile picture, and then download it, rather than doing it all in one query. I found that I had to force use of Graph v2.1 when querying the user’s friends. Otherwise, I got all the user’s friends and not only those who have installed the game. This version of the SDK should use v2.1 by default, so possibly a bug.

IEnumerator GetFacebookUserInfo(string graphURL, bool currentUser) {
    bool finishedGettingInfo = false;
    
    FacebookUserInfo userInfo = new FacebookUserInfo();
    
    // get user info
    FB.API(graphURL, Facebook.HttpMethod.GET, (FBResult userInfoResult) => {
        if (userInfoResult.Error != null) {
            Debug.LogError("Error getting FB friend info: " + userInfoResult.Error);
        }
        else {
            Dictionary<string, object> userInfoObjects = Facebook.MiniJSON.Json.Deserialize(userInfoResult.Text) as Dictionary<string, object>;
            userInfo.id = userInfoObjects["id"].ToString();
            userInfo.firstName = userInfoObjects["first_name"].ToString();
            userInfo.lastName = userInfoObjects["last_name"].ToString();
            userInfo.gender = userInfoObjects["gender"].ToString();
        }            
        finishedGettingInfo = true;
    });

    // wait until info got
    while (!finishedGettingInfo) {
        yield return null;
    }

    // if we successfully got info and id, get the profile picture
    if (userInfo.id != null) {           
        bool finishedGettingPictureURL = false;
        string pictureURL = null;
        
        // get profile picture url
        FB.API(FacebookUtil.GetPictureURL(userInfo.id, 128, 128), Facebook.HttpMethod.GET, (FBResult pictureResult) => {
            if (pictureResult.Error != null) {
                Debug.LogError("Error getting FB friend picture: " + pictureResult.Error);
            }
            else {
                pictureURL = FacebookUtil.DeserializePictureURLString(pictureResult.Text);
            }
            finishedGettingPictureURL = true;
        });

        // wait for it...
        while (!finishedGettingPictureURL) {
            yield return null;
        }
        
        // get profile picture
        if (pictureURL != null) {
            WWW www = new WWW(pictureURL);
            yield return www;
            userInfo.profilePicture = Sprite.Create(www.texture, 
                                                    new Rect(0, 0, 128, 128),
                                                    new Vector2(0.5f, 0.5f));
        }
    }

    // if we're getting this user's info
    if (currentUser) {
        CurrentUserFacebookUserInfo = userInfo;
        userInfoDownloadedEvent.Invoke();
        // now get friend user info
        if (FriendsPermissionGranted) {
            FB.API("/v2.1/me/friends", Facebook.HttpMethod.GET, OnFriendsDownloaded);
        }
    }
    // otherwise we were getting a friend's info. add it.
    else {
        FriendUserFacebookInfos.Add(userInfo);
    }
}

We don’t want to cause too much traffic, so lets get one friend info at a time. When all the friends info is downloaded, the event is fired. We use the same Coroutine as above to retrieve the user info.

void OnFriendsDownloaded(FBResult result) {
    if (result.Error != null) {
        Debug.LogError("Error getting FB friends: " + result.Error);
    }
    else {
        Dictionary<string, object> responseObject = Facebook.MiniJSON.Json.Deserialize(result.Text) as Dictionary<string, object>;            
        StartCoroutine(GetFriendsFacebookUserInfo(responseObject["data"] as List<object>));
    }
}

IEnumerator GetFriendsFacebookUserInfo(List<object> responseDataObjects) {
    int numFriends = responseDataObjects.Count;
    foreach (object friendDataObject in responseDataObjects) {
        Dictionary<string, object> friendDataObjectDict = friendDataObject as Dictionary<string, object>;
        // get one friend info at a time
        yield return StartCoroutine(GetFacebookUserInfo("/" + friendDataObjectDict["id"].ToString(), false));
        if (FriendUserFacebookInfos.Count == numFriends) {
            friendsInfoDownloadedEvent.Invoke();
        }
    }
}

Login and logout

We need a method to let the user login! If they aren’t logged in during initialisation that is.

public static void TryFBLogin() {
    if (instance != null) {
        #if UNITY_IPHONE || UNITY_ANDROID
        if (FB.IsInitialized && !FB.IsLoggedIn && LoginStatus == LoginStatusCode.NotLoggedIn) {
            LoginStatus = LoginStatusCode.AttemptingFBLogin;
            // just asking for some basic permissions. you can add more as required.
            FB.Login("public_profile,user_friends", instance.OnFBLoginAttempted);
        }
        #endif
    }
}

You can add any cleanup code you need to the logout Coroutine. The login status event will be fired when we set the status to “LoggingOut”. We yield for one frame afterwords to give any handler methods time to receive the event and act.

public static void LogoutUser() {
    if (instance != null && LoginStatus == LoginStatusCode.LoggedIn) {
        instance.StartCoroutine(instance.DoLogout());
    }
}

IEnumerator DoLogout() {
    while (LoginStatus == LoginStatusCode.AttemptingFBLogin) {
        yield return null;
    }
          
    LoginStatus = LoginStatusCode.LoggingOut;
    yield return null;

    // add any other cleanup code you need here

    CurrentUserFacebookUserInfo = new FacebookUserInfo();
    FriendUserFacebookInfos = new List<FacebookUserInfo>();

    if (FB.IsLoggedIn) {
        FB.Logout();
    }

    LoginStatus = LoginStatusCode.NotLoggedIn;
}

That’s all for now, good luck!

The complete script is available here. You also need the FacebookUtil class, taken from the FriendSmash example. I hope this helps you get started with Facebook integration. Thanks for reading!