Facebook Integration in LibGDX for iOS using Robovm Robopods, Tutorial Sample Example

By | March 16, 2016

Facebook Robopods tutorial
In this tutorial, we will very quickly integrate Facebook in LibGDX games. We will use RoboVM Robopods bindings for iOS part. We will be using Eclipse IDE for this tutorial.

PreRequisites:

  • You will need LibGDX of version 1.9.1+
  • RoboVM version of 1.12.0

If you have older versions of any of the above, then you can simply update them using the following:

Open the file named build.gradle in your libgdx project, and update the version for the following values:

        gdxVersion = '1.9.1'
        roboVMVersion = '1.14.0'

Then right click on your eclipse project -> Gradle-> Refresh All
This will update your roboVM and LibGDX. For more information you can read this.

Now Lets begin with the facebook integration part:

Adding facebook-ios Robovm Robopods library for gradle project as dependency:

– Open build.gradle file of your LibGDX project and add

project.ext.robopodsVersion = "1.14.0" 

in the section buildscript{} as follows:

buildscript {

   project.ext.robopodsVersion = "1.14.0"

    repositories {
        mavenCentral()
        maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
        jcenter()
    }
    dependencies {
        classpath 'de.richsource.gradle.plugins:gwt-gradle-plugin:0.6'
        classpath 'com.android.tools.build:gradle:1.0.0'
        classpath 'org.robovm:robovm-gradle-plugin:1.0.0'
    }
}

Now add the following line as dependency in project(“:ios”) {} section of the build.gradle file:

        compile "org.robovm:robopods-facebook-ios:$robopodsVersion"

This would look like something as shown below:

project(":ios") {
    apply plugin: "java"
    apply plugin: "robovm"

    configurations { natives }

    dependencies {
        compile project(":core")
        compile "org.robovm:robovm-rt:${roboVMVersion}"
        compile "org.robovm:robovm-cocoatouch:${roboVMVersion}"
        compile "com.badlogicgames.gdx:gdx-backend-robovm:$gdxVersion"
        natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-ios"
        natives "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-ios"
        compile "org.robovm:robopods-facebook-ios:$robopodsVersion"
    
    }
}

Now simply refresh your Gradle project by right-click-> Gradle-> Refresh All and thats it!
Look at the screenshot shown below for refreshing your project if you don’t know how to do it :
refreshing gradle project

Now to test whether the facebook library is successfully added or not, you can try to import the following in the IOSLauncher.java class file of your LibGDX iOS project.

import org.robovm.pods.facebook.core.FBSDKProfile;

If there is an error then try to refresh the gradle project by doing Refresh All,Refresh Dependencies, Refresh Sources for both your iOS project as well as main libGDX project.

NOTE: Please note that by doing refresh all on iOS project your other library projects if referenced through java build path will be removed. You can simply add them again.

Now since we have added the facebook iOS library successfully to our project, lets move forward.

Setting up the Facebook SDK in your project

Add the following in the info.plist.xml file of your iOS project.

               <key>FacebookAppID</key>
		<string>${fb.id}</string>
		<key>FacebookDisplayName</key>
		<string>${fb.name}</string>
		<key>CFBundleURLTypes</key>
		<array>
			<dict>
				<key>CFBundleURLSchemes</key>
				<array>
					<string>fb${fb.id}</string>
				</array>
			</dict>
		</array>
		<key>NSAppTransportSecurity</key>
		<dict>
			<key>NSAllowsArbitraryLoads</key>
			<true />
		</dict>

		<key>LSApplicationQueriesSchemes</key>
		<array>
			<string>fbapi</string>
			<string>fb-messenger-api</string>
			<string>fbauth2</string>
			<string>fbshareextension</string>
		</array>


The final info.plist.xml should look something like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
	<dict>
                <key>FacebookAppID</key>
		<string>${fb.id}</string>
		<key>FacebookDisplayName</key>
		<string>${fb.name}</string>
		<key>CFBundleURLTypes</key>
		<array>
			<dict>
				<key>CFBundleURLSchemes</key>
				<array>
					<string>fb${fb.id}</string>
				</array>
			</dict>
		</array>
		<key>NSAppTransportSecurity</key>
		<dict>
			<key>NSAllowsArbitraryLoads</key>
			<true />
		</dict>

		<key>LSApplicationQueriesSchemes</key>
		<array>
			<string>fbapi</string>
			<string>fb-messenger-api</string>
			<string>fbauth2</string>
			<string>fbshareextension</string>
		</array>
               <!-- --
                   -->
           </dict>
</plist>

Now open robovm.properties file and add the following at the end:

fb.name=YOUR APP NAME
fb.id=YOUR_FB_APP_ID

The final robovm.propeties file should look like:

app.version=1.0
app.id=com.tutorialsface.fbtutorial.IOSLauncher
app.mainclass=com.tutorialsface.fbtutorial.IOSLauncher
app.executable=IOSLauncher
app.build=1
app.name=YourProjectName
fb.name=YOUR APP NAME
fb.id=YOUR_FB_APP_ID

You need to obtain YOUR_FB_APP_ID and YOUR APP NAME by registering a new app on developer.facebook.com.
For more information about how to do it, visit this.

Thats it. Now we can start the coding part for implementing facebook login and accessing user profile and access token!

Coding Part:

Create a new java class named FacebookHandler.java in your iOS project and copy the following code:

package com.tutorialsface.apis;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.robovm.apple.foundation.NSArray;
import org.robovm.apple.foundation.NSDictionary;
import org.robovm.apple.foundation.NSError;
import org.robovm.apple.foundation.NSMutableDictionary;
import org.robovm.apple.foundation.NSObject;
import org.robovm.apple.foundation.NSOperationQueue;
import org.robovm.apple.foundation.NSString;
import org.robovm.apple.uikit.UIAlertView;
import org.robovm.objc.block.VoidBlock1;
import org.robovm.objc.block.VoidBlock2;
import org.robovm.objc.block.VoidBlock3;
import org.robovm.pods.facebook.core.FBSDKAccessToken;
import org.robovm.pods.facebook.core.FBSDKGraphRequest;
import org.robovm.pods.facebook.core.FBSDKGraphRequestConnection;
import org.robovm.pods.facebook.core.FBSDKProfile;
import org.robovm.pods.facebook.core.FBSDKProfileChangeNotification;
import org.robovm.pods.facebook.login.FBSDKDefaultAudience;
import org.robovm.pods.facebook.login.FBSDKLoginManager;
import org.robovm.pods.facebook.login.FBSDKLoginManagerLoginResult;

public class FacebookHandler {
	private static FacebookHandler instance = new FacebookHandler();
	private final FBSDKLoginManager loginManager;

	FBSDKProfile currentProfile;

	private FacebookHandler() {
		loginManager = new FBSDKLoginManager();
		loginManager.setDefaultAudience(FBSDKDefaultAudience.Everyone);
		FBSDKProfile.enableUpdatesOnAccessTokenChange(true);

		currentProfile = FBSDKProfile.getCurrentProfile();

		FBSDKProfile.Notifications
				.observeCurrentProfileDidChange(new VoidBlock1<FBSDKProfileChangeNotification>() {
					@Override
					public void invoke(
							FBSDKProfileChangeNotification notification) {
						if (notification != null
								&& notification.getNewProfile() != null) {
							currentProfile = notification.getNewProfile();
    //You can use the access token and public from here just after first login
							PrefsLoader.setLoggedIn(FacebookHandler
									.getInstance().getCurrentProfile()
									.getUserID(), FacebookHandler.getInstance()
									.getCurrentProfile().getName(),
									FBSDKAccessToken.getCurrentAccessToken()
											.getTokenString());

						}
					}
				});
	}

	public static FacebookHandler getInstance() {
		return instance;
	}

	public boolean isLoggedIn() {
		return FBSDKAccessToken.getCurrentAccessToken() != null;
	}

	public void logIn(final List<String> readPermissions,
			final LoginListener listener) {
		log("Trying to login with read permissions (%s)...", readPermissions);
		loginManager.logInWithReadPermissions(readPermissions,
				new VoidBlock2<FBSDKLoginManagerLoginResult, NSError>() {
					@Override
					public void invoke(FBSDKLoginManagerLoginResult result,
							NSError error) {
						if (error != null) {
							log("Failed to login: %s",
									error.getLocalizedDescription());
							listener.onError("An unknown error happened!");
						} else if (result.isCancelled()) {
							log("Cancelled login!");
							listener.onCancel();
						} else {
							if (!result.getGrantedPermissions().containsAll(
									readPermissions)) {
								log("Failed to login: Permissions declined (%s)",
										result.getDeclinedPermissions());
								listener.onError("The following permissions have been declined: "
										+ result.getDeclinedPermissions()
												.toString());
							} else {
								// result.getToken().getus
								log("Successfully logged in!");
								currentProfile = FBSDKProfile
										.getCurrentProfile();
								listener.onSuccess();
							}
						}
					}
				});
	}

	public void logOut() {
		loginManager.logOut();
		log("Successfully logged out!");
	}

	public FBSDKProfile getCurrentProfile() {
		return currentProfile;
	}

	public void alertError(String title, String message) {
		UIAlertView alert = new UIAlertView(title, message, null, "OK");
		alert.show();
	}

	public void requestFriends(RequestListener listener) {
		Map<String, String> params = new HashMap<String, String>();
		params.put("fields", "installed,name");
		requestGraph("me/friends", params, "GET", listener);
	}

	public void requestPermissionsIfNecessary(final List<String> permissions,
			final RequestListener listener) {
		log("Checking for required permissions (%s)...", permissions);
		if (isLoggedIn()) {
			requestGraph("me/permissions", null, "GET", new RequestListener() {
				@SuppressWarnings("unchecked")
				@Override
				public void onSuccess(NSObject result) {
					log("Successfully fetched permissions...");
					List<String> declinedPermissions = new ArrayList<String>(
							permissions);

					NSDictionary<NSString, ?> root = (NSDictionary<NSString, ?>) result;
					NSArray<NSDictionary<NSString, ?>> p = (NSArray<NSDictionary<NSString, ?>>) root
							.get(new NSString("data"));

					for (NSDictionary<NSString, ?> pData : p) {
						String permission = pData.get(
								new NSString("permission")).toString();
						boolean granted = "granted".equals(pData.get(
								new NSString("status")).toString());
						if (granted && declinedPermissions.contains(permission)) {
							declinedPermissions.remove(permission);
						}
					}

					if (declinedPermissions.size() == 0) {
						log("Required permissions are all granted!");
						if (listener != null) {
							listener.onSuccess(null);
						}
					} else {
						log("Missing required permission!");
						requestPublishPermissions(declinedPermissions,
								new LoginListener() {
									@Override
									public void onSuccess() {
										if (listener != null) {
											listener.onSuccess(null);
										}
									}

									@Override
									public void onError(String message) {
										if (listener != null) {
											listener.onError(message);
										}
									}

									@Override
									public void onCancel() {
										if (listener != null) {
											listener.onCancel();
										}
									}
								});
					}
				}

				@Override
				public void onError(String message) {
					log("Failed to fetch permissions: %s", message);
					if (listener != null) {
						listener.onError(message);
					}
				}

				@Override
				public void onCancel() {
					log("Cancelled fetch for permissions!");
					if (listener != null) {
						listener.onCancel();
					}
				}
			});
		} else {
			log("Not logged in!");
			if (listener != null) {
				listener.onError(null);
			}
		}
	}

	public void requestPublishPermissions(
			final List<String> publishPermissions, final LoginListener listener) {
		log("Requesting publish permissions (%s)...", publishPermissions);
		loginManager.logInWithPublishPermissions(publishPermissions,
				new VoidBlock2<FBSDKLoginManagerLoginResult, NSError>() {
					@Override
					public void invoke(FBSDKLoginManagerLoginResult result,
							NSError error) {
						if (error != null) {
							log("Failed to request publish permissions: %s",
									error);
							listener.onError("An unknown error happened!");
						} else if (result.isCancelled()) {
							log("Cancelled request for publish permissions!");
							listener.onCancel();
						} else {
							if (!result.getGrantedPermissions().containsAll(
									publishPermissions)) {
								log("Failed to request publish permissions: Permissions declined (%s)",
										result.getDeclinedPermissions());
								listener.onError("The following permissions have been declined: "
										+ result.getDeclinedPermissions()
												.toString());
							} else {
								log("Successfully requested publish permissions");
								listener.onSuccess();
							}
						}
					}
				});
	}

	public void publishFeed(final String name, final String description,
			final String message, final String link, final String pictureUrl,
			final RequestListener listener) {
		requestPermissionsIfNecessary(Arrays.asList("publish_actions"),
				new RequestListener() {
					@Override
					public void onSuccess(NSObject result) {
						Map<String, String> params = new HashMap<String, String>();
						params.put("name", name);
						params.put("description", description);
						params.put("message", message);
						params.put("link", link);
						params.put("picture", pictureUrl);

						requestGraph("me/feed", params, "POST", listener);
					}

					@Override
					public void onError(String message) {
						if (listener != null) {
							listener.onError(message);
						}
					}

					@Override
					public void onCancel() {
						if (listener != null) {
							listener.onCancel();
						}
					}
				});
	}

	public void requestGraph(final String path,
			final Map<String, String> params, final String httpMethod,
			final RequestListener listener) {
		NSOperationQueue.getMainQueue().addOperation(new Runnable() {
			@Override
			public void run() {
				FBSDKGraphRequestConnection connection = new FBSDKGraphRequestConnection();
				FBSDKGraphRequest request = new FBSDKGraphRequest(path,
						convertStringMapToDictionary(params), httpMethod);
				log("Requesting graph path %s...", path);
				connection
						.addRequest(
								request,
								new VoidBlock3<FBSDKGraphRequestConnection, NSObject, NSError>() {
									@Override
									public void invoke(
											FBSDKGraphRequestConnection connection,
											NSObject result, NSError error) {
										if (error != null) {
											log("Failed to request graph path: %s",
													error.getLocalizedDescription());
											if (listener != null) {
												listener.onError(error
														.getLocalizedDescription());
											}
										} else {
											log("Successfully requested graph path %s!",
													path);
											if (listener != null) {
												listener.onSuccess(result);
											}
										}
									}
								});
				connection.start();
			}
		});
	}

	private NSDictionary<NSString, NSString> convertStringMapToDictionary(
			Map<String, String> map) {
		NSDictionary<NSString, NSString> result = new NSMutableDictionary<>();
		if (map != null) {
			for (Map.Entry<String, String> entry : map.entrySet()) {
				result.put(new NSString(entry.getKey()),
						new NSString(entry.getValue()));
			}
		}
		return result;
	}

	public static void log(String message, Object... args) {
		System.out.println(String.format(message, args));
	}

	public interface LoginListener {
		void onSuccess();

		void onError(String message);

		void onCancel();
	}

	public interface RequestListener {
		void onSuccess(NSObject result);

		void onError(String message);

		void onCancel();
	}
}

You will face an error about PrefsLoader class. This class we will create now to store facebook user’s Name, id and accessToken in your core project of LibGDX.
Lets create a new class named PrefsLoader.java in core project Of LibGDX (NOT in iOS project) and copy the following content:


public class PrefsLoader {
	public static String fbid, token, fbname;
	public static boolean isLoggedIn;
	public static String profile_pic_url = "";

public static void setLoggedIn(String id, String name, String token) {
		isLoggedIn = true;
		profile_pic_url = "https://graph.facebook.com/" + id
				+ "/picture?type=square";

		PrefsLoader.fbid = id;
		PrefsLoader.token = token;
                PrefsLoader.fbname=name;
          }
}

Hence our implentation of facebook is completed, now lets see how to use this code for logging into facebook through the LibGDX core project.

We will do this by using interfaces. so Lets begin..

Create a new class named ListenerManager.java in your core project with following content:

import java.util.ArrayList;

public class ListenerManager {
	ArrayList<Listener> listeners;

	public ListenerManager() {
		listeners = new ArrayList<Listener>();
	}

	public void add(Listener l) {
		if (listeners == null) {
			listeners = new ArrayList<Listener>();
		}
		listeners.add(l);
	}

	public void call(ListenerType type) {
		for (Listener l : listeners) {
			if (l.type() == type) {
				l.call();
			}
		}
	}

	public enum ListenerType {
		 FACEBOOK_LOGIN
	}
}

Now create another class named Listener.java in core project :

public interface Listener {

	   public abstract void call();
	
	   public abstract ListenerType type();
	    
}

Now instantiate the ListenerManager in the main class of your core project with static reference so that we can use that reference everywhere in the core project. To do this, copy below code in your main class of the core project:


public static ListenerManager listenerManager;

	public void addListener(Listener l) {
		listenerManager.add(l);
	}

You main class that is the class that launches for the core project might look something like:

public class MainGame extends Game {
// Intstantiating listenerManager with static reference
	public static ListenerManager listenerManager;

	public void addListener(Listener l) {
		listenerManager.add(l);
	}

	public MainGame() {

		listenerManager = new ListenerManager();
	}

	@Override
	public void create() {
		// Logging in to Facebook from here..
		MainGame.listenerManager.call(ListenerType.FACEBOOK_LOGIN);
	
	}

	@Override
	public void dispose() {
		super.dispose();
	}

}

Look at the line

MainGame.listenerManager.call(ListenerType.FACEBOOK_LOGIN);

in the oncreate() method.
This is the way to initiate facebook login from the core project.

Just use the below line at anywhere in your core project to start facebook login from there.

MainGame.listenerManager.call(ListenerType.FACEBOOK_LOGIN);

So we have created interface in the core project and have also called the method to start the logging process of facebook but have not implemented this method yet.

Lets write the implementation for the above interfaces we just created. This implementation will go in the iOS project.

Create a new class named LoginListener.java in your iOS project with the following content:


public class LoginListener implements Listener {
	IOSLauncher base;

	public LoginListener(IOSLauncher act) {
		base = act;
	}

	@Override
	public void call() {
		if (!FacebookHandler.getInstance().isLoggedIn()) {

			FacebookHandler.getInstance().logIn(
					Arrays.asList("public_profile", "user_friends"),
					new FacebookHandler.LoginListener() {
						@Override
						public void onSuccess() {

						}

						@Override
						public void onError(String message) {
							FacebookHandler.getInstance().alertError(
									"Error during login!", message);
						}

						@Override
						public void onCancel() {
							// User cancelled, so do nothing.
						}
					});
		} 
	}

	@Override
	public ListenerType type() {
		return ListenerType.FACEBOOK_LOGIN;

	}

}

Now add this listener’s instance to the core project through the IOSLauncher by calling the addListener method which we defined in the Main class of core project.
Add the following code in createApplication() method of IOSLauncher:


MainGame game = new MainGame();
      // Adding the login interface Implementation to the core project-------
		LoginListener login = new LoginListener(this);
		game.addListener(login);
//-----------------

Hence we have completed the facebook login implementation. Facebook silently gets logged in automatically after the first login, every time the app is restarted.

For facebook login to work properly, you need to add the following in IOSLauncher.java:

       @Override
	public boolean didFinishLaunching(UIApplication application,
			UIApplicationLaunchOptions launchOptions) {
		super.didFinishLaunching(application, launchOptions);

		FBSDKApplicationDelegate.getSharedInstance().didFinishLaunching(
				application, launchOptions);
          return true;
     }
	@Override
	public boolean openURL(UIApplication application, NSURL url,
			String sourceApplication, NSPropertyList annotation) {
		super.openURL(application, url, sourceApplication, annotation);
		return FBSDKApplicationDelegate.getSharedInstance().openURL(
				application, url, sourceApplication, annotation);
	}

	@Override
	public void didBecomeActive(UIApplication application) {
		super.didBecomeActive(application);
		FBSDKAppEvents.activateApp();
	}

You have to manually update the access token, id, name etc. every time so that your app knows that its already connected to facebook. So to do this, Just use the following code in the didFinishLaunching() method of your IOSLauncher.java:

	if (FacebookHandler.getInstance().isLoggedIn()) {
			PrefsLoader.setLoggedIn(FBSDKProfile.getCurrentProfile()
					.getUserID(), FBSDKProfile.getCurrentProfile().getName(),
					FBSDKAccessToken.getCurrentAccessToken().getTokenString());

		}

The final IOSLauncher.java would look like:

public class IOSLauncher extends IOSApplication.Delegate {

	@Override
	protected IOSApplication createApplication() {

		IOSApplicationConfiguration config = new IOSApplicationConfiguration();
		config.orientationLandscape = true;
		config.orientationPortrait = false;
		MainGame game = new MainGame();
      // Adding the login interface Implementation to the core project-------
		LoginListener login = new LoginListener(this);
		game.addListener(login);
//-----------------

		return  new IOSApplication(game, config);
	}

	public static void main(String[] argv) {
		NSAutoreleasePool pool = new NSAutoreleasePool();
		UIApplication.main(argv, null, IOSLauncher.class);
		pool.close();
	}


	@Override
	public boolean didFinishLaunching(UIApplication application,
			UIApplicationLaunchOptions launchOptions) {
		super.didFinishLaunching(application, launchOptions);

		FBSDKApplicationDelegate.getSharedInstance().didFinishLaunching(
				application, launchOptions);

		if (FacebookHandler.getInstance().isLoggedIn()) {
			PrefsLoader.setLoggedIn(FBSDKProfile.getCurrentProfile()
					.getUserID(), FBSDKProfile.getCurrentProfile().getName(),
					FBSDKAccessToken.getCurrentAccessToken().getTokenString());

		}
		
		return true;
	}

	@Override
	public boolean openURL(UIApplication application, NSURL url,
			String sourceApplication, NSPropertyList annotation) {
		super.openURL(application, url, sourceApplication, annotation);
		return FBSDKApplicationDelegate.getSharedInstance().openURL(
				application, url, sourceApplication, annotation);
	}

	@Override
	public void didBecomeActive(UIApplication application) {
		super.didBecomeActive(application);
		FBSDKAppEvents.activateApp();
	}

Now the implementation is complete. Lets look again at the call to be made in the core project for logging in:

MainGame.listenerManager.call(ListenerType.FACEBOOK_LOGIN);

You simply have to make this call to login to facebook. After making this call, you will be taken to the safari for login. Facebook has explained why they want to use safari for login instead of native facebook app here.

Lets look at what all changes you have to make on your project for facebook integration in a figurative way:
Facebook LibGDX tutorial

Finally we have successfully integrated facebook in a LibGDX project for iOS using RoboVm Robopods Gradle project.
This is a little tedious work but trust me you will get it done, if you follow the complete proccess carefully.
Thank You!

1,940 total views, 5 views today

(Visited 730 times, 1 visits today)
  • iQue

    Thanks for this. Can you please make a tutorial for using LibGdx pay, there are no recent/detailed tutorials using this library.