How to use Facebook Graph API on iOS properly - iphone

I am making an application where I need to invoke requests in multiple view controllers. I don't want the Facebook log in screen to show when the app is launched. It should only open when the user taps a button in another view controller if the user is not logged in, so putting the code in app delegate is not working solution to the problem. And the whole asynchronous process of requesting data in the Facebook API is killing me, I am starting to loose hair.
A class with static methods to get the facebook object and treat it as a singleton.
#import "PBSocialMedia.h"
#import "PBFacebookDelegate.h"
static Facebook *facebook;
static PBFacebookDelegate *facebookDelegate;
#implementation PBSocialMedia
+ (Facebook *)sharedFacebook {
if (facebook == nil) {
facebookDelegate = [[PBFacebookDelegate alloc] init];
facebook = [[Facebook alloc] initWithAppId:#"156687441079334" andDelegate:facebookDelegate];
}
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"FBAccessTokenKey"]
&& [defaults objectForKey:#"FBExpirationDateKey"]) {
facebook.accessToken = [defaults objectForKey:#"FBAccessTokenKey"];
facebook.expirationDate = [defaults objectForKey:#"FBExpirationDateKey"];
}
NSArray *permissions = [NSArray arrayWithObjects:#"user_photo_video_tags", #"friends_photo_video_tags", #"user_photos", #"friends_photos", nil];
if (![facebook isSessionValid]) {
[facebook authorize:permissions];
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:#"connectedToFacebook" object:self];
}
return facebook;
}
+ (Facebook *)sharedFacebookWithoutAuthorizing {
return facebook;
}
#end
A class that implements the Facebook session delegate used in the class above
#import "PBFacebookDelegate.h"
#import "PBSocialMedia.h"
#implementation PBFacebookDelegate
/**
* Called when the user successfully logged in.
*/
- (void)fbDidLogin {
Facebook *facebook = [PBSocialMedia sharedFacebook];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[facebook accessToken] forKey:#"FBAccessTokenKey"];
[defaults setObject:[facebook expirationDate] forKey:#"FBExpirationDateKey"];
[defaults synchronize];
[[NSNotificationCenter defaultCenter] postNotificationName:#"connectedToFacebook" object:self];
}
/**
* Called when the user dismissed the dialog without logging in.
*/
- (void)fbDidNotLogin:(BOOL)cancelled {
[[NSNotificationCenter defaultCenter] postNotificationName:#"dismissedFacebookDialog" object:self];
}
/**
* Called when the user logged out.
*/
- (void)fbDidLogout {
[[NSNotificationCenter defaultCenter] postNotificationName:#"disconnectedFromFacebook" object:self];
}
#end
As you see from the two classes above I am making use of notifications so that I know when the user logs in/out of Facebook. The code below is an example of how I am trying to use it:
- (void)viewDidLoad {
[super viewDidLoad];
Facebook *facebook = [PBSocialMedia sharedFacebook];
if (self.canUseGraphAPI) {
[facebook requestWithGraphPath:#"106378916135129/photos" andDelegate:self];
NSLog(#"Can use");
} else {
NSLog(#"Can't use");
}
}
- (void)connectedToFacebook {
self.canUseGraphAPI = YES;
}
- (void)disconnectedFromFacebook {
self.canUseGraphAPI = NO;
}
This is ofcourse NOT working as intended because of the stupid asynchronous request. It will work correct only when the user is logged in. So the first time I open the app it invokes the [PBSocialMedia sharedFacebook]; Then it proceed with the request function AND NOT waiting for the sharedFacebook function to finish. I really hope somebody has encountered this problem before. I want to do requests in different places in my code, but only when the user is logged in, and it need to WAIT while the user logs in before requesting. The code below is not working but it shows what I want!
- (void)viewDidLoad {
[super viewDidLoad];
Facebook *facebook = [PBSocialMedia sharedFacebook];
while (self.canUseGraphAPI) {
// WAITING FOR LOGIN IN
}
[facebook requestWithGraphPath:#"106378916135129/photos" andDelegate:self];
}
- (void)connectedToFacebook {
self.canUseGraphAPI = YES;
}
- (void)disconnectedFromFacebook {
self.canUseGraphAPI = NO;
}

I would start restructuring your code to work by passing in blocks that you want executed upon getting your answer back from FB. This is similar to how you do ajax in JQuery.
- (void)viewDidLoad {
[super viewDidLoad];
Facebook *facebook = [PBSocialMedia sharedFacebook];
[self withGraphAPIDo: ^{
[facebook requestWithGraphPath:#"106378916135129/photos" andDelegate:self];
}];
}

Related

Setting up connection to Facebook not working as expected (iPhone)

I'm trying to create a connection to Facebook however I'm having an issue with the handling of the openUrl.
In the past I've been able to add the following in my app delegate class:
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
return [[viewController facebook] handleOpenURL:url];
}
Which has worked as I would expect. However, this time I have a slightly different situation in the sense that the viewController is loaded elsewhere in the app. To get around this problem I came up with an idea of creating a new class that is responsible for handling the connection, but can also be accessed from the class where I create the Facebook post.
To explain further here is the relevant code in my app delegate class
.m
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
FacebookConnectionHandler *fbConnHandler = [[FacebookConnectionHandler alloc] init];
return [[fbConnHandler facebook] handleOpenURL:url];
}
Then here is the code in the FacebookConnectionHandler class:
.h
#import <Foundation/Foundation.h>
#import "Other_ViewController.h"
#import "Facebook.h"
#interface FacebookConnectionHandler : NSObject <FBSessionDelegate>
{
Other_ViewController *otherView;
Facebook *facebook;
}
#property(nonatomic, strong)Other_ViewController *otherView;
#property(nonatomic, strong)Facebook *facebook;
+ (id)sharedManager;
#end
.m
#import "FacebookConnectionHandler.h"
#implementation FacebookConnectionHandler
#synthesize otherView;
#synthesize facebook;
static FacebookConnectionHandler *mySingleton = nil;
+ (id)sharedManager
{
#synchronized(self)
{
if (mySingleton == nil) mySingleton = [[self alloc] init];
}
return mySingleton;
}
- (void)fbDidLogin
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[facebook accessToken] forKey:#"FBAccessTokenKey"];
[defaults setObject:[facebook expirationDate] forKey:#"FBExpirationDateKey"];
[defaults synchronize];
// Allow the user to create a post
[self.otherView createFacebookPost];
}
#end
Finally... here is the relevant code in the Other_ViewController class (where the post is being created):
.h
#import "FBConnect.h"
#interface Other_ViewController : UIViewController <FBSessionDelegate>
{
Facebook *facebook;
}
#property(nonatomic, retain)Facebook *facebook;
- (void)createFacebookPost;
#end
.m
- (void)createFacebookPost
{
// Create the post
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
#"Blah", #"name",
#"", #"caption",
#"", #"description",
#"http://www.xyz.com", #"link",
#"", #"picture",
nil];
// Post it to the users feed
[facebook dialog:#"feed" andParams:params andDelegate:nil];
}
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
switch (buttonIndex)
{
case kFacebookButton:
{
if (facebook == nil || ![facebook isSessionValid])
{
// Setup Facebook connection
facebook = [[Facebook alloc] initWithAppId:#"1111111111" andDelegate:self];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"FBAccessTokenKey"]
&& [defaults objectForKey:#"FBExpirationDateKey"])
{
facebook.accessToken = [defaults objectForKey:#"FBAccessTokenKey"];
facebook.expirationDate = [defaults objectForKey:#"FBExpirationDateKey"];
}
// Set the connection handler
FacebookConnectionHandler *fbConnectionHandler = [[FacebookConnectionHandler alloc] init];
fbConnectionHandler.mapView = self;
fbConnectionHandler.facebook = self.facebook;
if (![facebook isSessionValid])
{
NSArray *permissions = [[NSArray alloc] initWithObjects:#"publish_actions", nil];
[facebook authorize:permissions];
}
}
else
{
// Create the post
[self createFacebookPost];
}
break;
}
default:
break;
}
}
I may be going about this in completely the wrong way and have completely overcomplicated the problem, however I'm new to the whole facebook SDK and at this point I'm really stumped. Please can someone offer a solution?
Note: To be clear, the issue is that the method fbDidLogin is not being called, thus the rest of the code doesn't get a chance to run.
You're not using your singleton in the app delegate, you're creating a new instance of your connect handler class :
Instead of
FacebookConnectionHandler *fbConnHandler = [[FacebookConnectionHandler alloc] init];
return [[fbConnHandler facebook] handleOpenURL:url];
try
return [[[FacebookConnectionHandler sharedManager] facebook] handleOpenURL:url];
You're also not using the singleton in your Other_ViewController class.
If you are going to have the singleton architecture pattern you have to remember to always use the sharedManager and never alloc/init a new one :)
I sometimes make init throw an exception to remind me that there is a singleton method.
static FacebookConnectionHandler *mySingleton = nil;
- (id)init {
#throw [NSException exceptionWithName:self.class.description reason:#"Please use the sharedManager, don't make a new one of these!" userInfo:nil];
}
- (id)initInternal {
// Put your real init stuff in here
}
+ (id)sharedManager
{
#synchronized(self)
{
if (mySingleton == nil) mySingleton = [[self alloc] initInternal];
}
return mySingleton;
}
PS Using a separate Facebook class is exactly the way I've done it in apps I've written before - your architecture is fine :) I would also consider making the Facebook connection hander class responsible for making it's own Facebook instance instead of the view controller having to do it :)

Facebook always reauthorizing on each app launch?

I have recently added Facebook integration to my app, and everything works fine except when the user relaunches the app. Each time the app is restarted, Facebook has to go back through its authorization process. This involves switching out of the app to Safari/Facebook, then back to my app. How can I make Facebook save the info, or be able to get blanket permissions for my app so that I doesn't constantly reauthorize?
Here is my code from the applicationDidFinishLaunchingWithOptions: method:
facebook = [[Facebook alloc] initWithAppId:#"203604286395694" andDelegate:self];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"FBAccessTokenKey"]
&& [defaults objectForKey:#"FBExpirationDateKey"]) {
facebook.accessToken = [defaults objectForKey:#"FBAccessTokenKey"];
facebook.expirationDate = [defaults objectForKey:#"FBExpirationDateKey"];
}
if (![facebook isSessionValid]) {
[facebook authorize:nil];
}
Relevant Facebook delegate methods:
- (void)fbDidLogin {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[facebook accessToken] forKey:#"FBAccessTokenKey"];
[defaults setObject:[facebook expirationDate] forKey:#"FBExpirationDateKey"];
[defaults synchronize];
}
- (void) requestDialogButtonClicked {
NSMutableDictionary* params =
[NSMutableDictionary dictionaryWithObjectsAndKeys:
#"invites you to check out cool stuff", #"message",
#"Check this out", #"notification_text",
nil];
[facebook dialog:#"apprequests"
andParams:params
andDelegate:self];
}
- (void)dialogDidComplete:(FBDialog *)dialog {
NSLog(#"dialog completed successfully");
}
Are you calling Facebook authorize: somewhere? That's how the fbDidLogin will be triggered. Assuming you are, the behavior indicates that either the token or the date (or both) is nil, or that the date is past expiration (unlikely).
Log the values upon writing and reading the user defaults. My guess is that the authorize either wasn't called, or failed (due to app key or some other problem not shown in the code), leaving the fbDidLogin method uncalled or saving nil values.
That's because you are not asking for permissions when you authorize your app .. "offline_access" provide a long term access token so you will not need to get a new access token every time the user wants to use the Facebook .. so what you need to authorize your app like this
if (![facebook isSessionValid]) {
NSArray *permissions = [[NSArray alloc] initWithObjects:
#"user_likes",
#"read_stream",
#"offline_access"
nil];
[facebook authorize:permissions];
[permissions release];
}
Edit :
Please check the following in your implementation :
1.you need to add the following functions in your application delegate(the Facebook object here is your instance value of FaceBook Class).
// Pre 4.2 support
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url
{
return [facebook handleOpenURL:url];
}
// For 4.2+ support
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
return [facebook handleOpenURL:url];
}
2.In your info.plist add to URL types > URL Schemes > your Facebook app ID with fb prefix (finally your value will be like this for ex. fb313714***).

Facebook Dialog Delegate callback value

Trying to implement Facebook iOS SDK on my iphone App. I am using FBDialog Delegate to allow the user to login and get the response/callback using the delegate.
I would like to grab the code in response_type from the user once the login is successful.
Can anyone help me out here or let me know where to take the guide from.
I have the following code which is implemented :
-(void) login{
self.fb = [[Facebook alloc] initWithAppId:AppID andDelegate:self];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"FBAccessTokenKey"]
&& [defaults objectForKey:#"FBExpirationDateKey"]) {
self.fb.accessToken = [defaults objectForKey:#"FBAccessTokenKey"];
self.fb.expirationDate = [defaults objectForKey:#"FBExpirationDateKey"];
}
fb.sessionDelegate = self;
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:AppID forKey:#"client_id"];
[params setObject:#"code" forKey:#"response_type"];
[fb dialog:#"oauth" andParams:params andDelegate:self];
NSLog(#"Callback: %#", params);
}
In didReceiveResponse, the delegate method called when response is beginning (not yet complete and parsed), get the statusCode from the response object.
- (void)request:(FBRequest *)request didReceiveResponse:(NSURLResponse *)response {
if (200 == [response statusCode]) {
NSLog(#"Success");
}
}
Just handle the delegate methods:
/**
* Your application should implement this delegate to receive session callbacks.
*/
#protocol FBSessionDelegate <NSObject>
#optional
/**
* Called when the user successfully logged in.
*/
- (void)fbDidLogin;
/**
* Called when the user dismissed the dialog without logging in.
*/
- (void)fbDidNotLogin:(BOOL)cancelled;
/**
* Called when the user logged out.
*/
- (void)fbDidLogout;
#end

Facebook SDK method 'fbDidLogin' never gets called after user authentication (iOS)

I'm having a lot of trouble posting comments to Facebook. Here is how I have got the code setup; in my viewController class I have set up a button that when pressed checks if the user is connected to Facebook. If they're not connected I attempts to make a connection. Here is the code the .h and .m files:
.h file
#import <UIKit/UIKit.h>
#import "FBConnect.h"
#interface Info_Main_ViewController : UIViewController <FBSessionDelegate>
{
Facebook *facebook;
}
#property(nonatomic, retain)Facebook *facebook;
- (IBAction)shareButtonPressed:(id)sender;
- (void)setUpFacebookConnection;
.m file
- (IBAction)shareButtonPressed:(id)sender
{
if (facebook == nil)
{
// Setup Facebook connection
[self setUpFacebookConnection];
}
}
- (void)setUpFacebookConnection
{
facebook = [[Facebook alloc] initWithAppId:#"xxxxxx" andDelegate:self];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"FBAccessTokenKey"]
&& [defaults objectForKey:#"FBExpirationDateKey"])
{
facebook.accessToken = [defaults objectForKey:#"FBAccessTokenKey"];
facebook.expirationDate = [defaults objectForKey:#"FBExpirationDateKey"];
}
if (![facebook isSessionValid])
{
NSArray *permissions = [[NSArray alloc] initWithObjects:
#"publish_actions",
nil];
[facebook authorize:permissions];
}
}
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
return [facebook handleOpenURL:url];
}
- (void)fbDidLogin
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[facebook accessToken] forKey:#"FBAccessTokenKey"];
[defaults setObject:[facebook expirationDate] forKey:#"FBExpirationDateKey"];
[defaults synchronize];
}
When I run this code and press the button it opens up safari with a page allowing the user to choose whether to allow the app access to Facebook. Once you accept this it switched back to my app, however the method fbDidLogin never gets called so I can't ever post any messages.
Please can someone help me out, I've spent ages trawling the internet for advice but all of the relevant articles are either outdated or they have the connection to Facebook setup in the app delegate class. Whereas in my situation I only want to user to connect when they choose (i.e. when they press the button).
This method:
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
return [facebook handleOpenURL:url];
}
must be placed in your application delegate. You will have to change the reference to the facebook instance so that it's valid in the app delegate, of course.

iphone app delegate with logic + facebook connect?

i'm really stuck on this one so please help!
I'm writing an app that implements facebook connect so when the app starts, I need it to check to see if it has a valid facebook access token, AND also check if their appKey that i provide is still valid (i don't want more than 1 account logged in at a time). so what needs to happen is..
App starts -> get facebook/my access key/token from NSUserDefaults -> send my appkey to a server to make sure it's still valid -> if valid then show my tableviewcontroller.
if it fails anywhere else(facebook access token isn't valid, or their appkey for my app isn't valid), then they will be taken to a View with the facebook connect button. after they login from there, they will be shown the tableviewcontroller
I don't know how to structure my app delegate and view controllers for this to work. From what I know about the facebook connect, most of the stuff has to happen in the delegate because facebook's application:handleOpenUrl: and fbDidLogin methods have to be called in the app delegate but if i do a
self.window.rootViewController = self.tableController
or
self.window.rootViewController = self.loginButtonViewController
before that, then i won't have access to these methods
Do i need to put in a delegate or something from the view controller back to the app delegate? i have no clue..
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
facebook = [[Facebook alloc] initWithAppId:#"MY_APP_ID"];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"FBAccessTokenKey"]
&& [defaults objectForKey:#"FBExpirationDateKey"]) {
facebook.accessToken = [defaults objectForKey:#"FBAccessTokenKey"];
facebook.expirationDate = [defaults objectForKey:#"FBExpirationDateKey"];
}
NSString *myKey;
if ([defaults stringForKey:#"myKey"]) {
myKey= [[NSString alloc] initWithString:[defaults stringForKey:#"myKey"]];
}
else{
myKey = [[NSString alloc] initWithString:#""];
}
//SEND THE KEY + FBID TO SERVER
if ([facebook isSessionValid] /*&& [response==OK]*/) {
self.window.rootViewController = self.navController;
//delegate data to EventsTableController
}
else{
self.window.rootViewController = self.loginController;
}
[self.window makeKeyAndVisible];
return YES;
}
- (void)fbDidLogin {
NSLog(#"did login");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[facebook accessToken] forKey:#"FBAccessTokenKey"];
[defaults setObject:[facebook expirationDate] forKey:#"FBExpirationDateKey"];
[defaults synchronize];
NSString *myKey;
[facebook requestWithGraphPath:#"me" andDelegate:self];
if ([defaults stringForKey:#"myKey"]) {
myKey= [[NSString alloc] initWithString:[defaults stringForKey:#"myKey"]];;
}
else{
myKey= [[NSString alloc] initWithString:#""];
}
//NSString *validKey = [[NSString alloc] initWithString:#"OK"];
//Send myKey and validKey to server
//server will do its thang and send data
[self.window addSubview:self.navController.view];
[self.window makeKeyAndVisible];
}
thanks in advance
What means myKey?
In facebook.h, the method isSessionValid uses only two variables. (accessToken, expirationDate)
- (BOOL)isSessionValid {
return (self.accessToken != nil && self.expirationDate != nil
&& NSOrderedDescending == [self.expirationDate compare:[NSDate date]]);
}
You should have moved above code to RootViewController.m not AppDelegate.m
Because MainWindow.xib can recognize only one RootView.
(In your case, you wanted to have more RootView, navController, loginController)
See this Page
So, I suggest that you move your authorization code to RootViewController.m
(etc. viewDidLoad or viewWillAppear methods)
Next, in RootViewController, try to change your view according to whether session is valid or not.
And try Again! It will work!