presentRequestsDialogModallyWithSession does not work, but gives good result - api

When I use the webdialog for a friendrequest, everything is going fine, except no request or anything is made.
The code:
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
facebookFriend.id, #"to",
nil];
[FBWebDialogs presentRequestsDialogModallyWithSession:FBSession.activeSession
message:NSLocalizedString(#"FB_FRIEND_INVITE_MESSAGE", #"Facebook friend invite message")
title:NSLocalizedString(#"FB_FRIEND_INVITE_TITLE", #"Facebook friend invite title")
parameters:params
handler:^(FBWebDialogResult result, NSURL *resultURL, NSError *error) {
}];
This is the result I get:
fbconnect://success?request=xxxxxxxxxxxx&to%5B0%5D=xxxxxxxx
How can I debug what is going wrong?
Thanks in advance.
Ruud

For SDK 3.2 or above we have a facility to use FBWebDialogs class that will help us to show a popup along with the friend list and pick one or more from list to send invitation.
Lets do it step by step:
1) Download and setup SDK 3.2 or above.
2) First setup your application on facebook by following this url.
3) Then use the attached code.
Sample Code: (It generates invite friend request)
-(void)inviteFriends
{
if ([[FBSession activeSession] isOpen])
{
NSMutableDictionary* params = [NSMutableDictionary dictionaryWithObjectsAndKeys:nil];
[FBWebDialogs presentRequestsDialogModallyWithSession:nil
message:[self getInviteFriendMessage]
title:nil
parameters:params
handler:^(FBWebDialogResult result, NSURL *resultURL, NSError *error)
{
if (error)
{
[self requestFailedWithError:error];
}
else
{
if (result == FBWebDialogResultDialogNotCompleted)
{
[self requestFailedWithError:nil];
}
else if([[resultURL description] hasPrefix:#"fbconnect://success?request="])
{
// Facebook returns FBWebDialogResultDialogCompleted even user
// presses "Cancel" button, so we differentiate it on the basis of
// url value, since it returns "Request" when we ACTUALLY
// completes Dialog
[self requestSucceeded];
}
else
{
// User Cancelled the dialog
[self requestFailedWithError:nil];
}
}
}
];
}
else
{
/*
* open a new session with publish permission
*/
[FBSession openActiveSessionWithPublishPermissions:[NSArray arrayWithObject:#"publish_stream"]
defaultAudience:FBSessionDefaultAudienceFriends
allowLoginUI:YES
completionHandler:^(FBSession *session, FBSessionState status, NSError *error)
{
if (!error && status == FBSessionStateOpen)
{
NSMutableDictionary* params = [NSMutableDictionary dictionaryWithObjectsAndKeys:nil];
[FBWebDialogs presentRequestsDialogModallyWithSession:nil
message:[self getInviteFriendMessage]
title:nil
parameters:params
handler:^(FBWebDialogResult result, NSURL *resultURL, NSError *error)
{
if (error)
{
[self requestFailedWithError:error];
}
else
{
if (result == FBWebDialogResultDialogNotCompleted)
{
[self requestFailedWithError:nil];
}
else if([[resultURL description] hasPrefix:#"fbconnect://success?request="])
{
// Facebook returns FBWebDialogResultDialogCompleted even user
// presses "Cancel" button, so we differentiate it on the basis of
// url value, since it returns "Request" when we ACTUALLY
// completes Dialog
[self requestSucceeded];
}
else
{
// User Cancelled the dialog
[self requestFailedWithError:nil];
}
}
}];
}
else
{
[self requestFailedWithError:error];
}
}];
}
}
and here are the helper functions that calls delegates function OnFBSuccess and OnFBFailed.
- (void)requestSucceeded
{
NSLog(#"requestSucceeded");
id owner = [fbDelegate class];
SEL selector = NSSelectorFromString(#"OnFBSuccess");
NSMethodSignature *sig = [owner instanceMethodSignatureForSelector:selector];
_callback = [NSInvocation invocationWithMethodSignature:sig];
[_callback setTarget:owner];
[_callback setSelector:selector];
[_callback retain];
[_callback invokeWithTarget:fbDelegate];
}
- (void)requestFailedWithError:(NSError *)error
{
NSLog(#"requestFailed");
id owner = [fbDelegate class];
SEL selector = NSSelectorFromString(#"OnFBFailed:");
NSMethodSignature *sig = [owner instanceMethodSignatureForSelector:selector];
_callback = [NSInvocation invocationWithMethodSignature:sig];
[_callback setTarget:owner];
[_callback setSelector:selector];
[_callback setArgument:&error atIndex:2];
[_callback retain];
[_callback invokeWithTarget:fbDelegate];
}
So the class taht calls method InviteFriend MUST have these functions:
-(void)OnFBSuccess
{
CCLOG(#"successful");
// do stuff here
[login release];
}
-(void)OnFBFailed:(NSError *)error
{
if(error == nil)
CCLOG(#"user cancelled");
else
CCLOG(#"failed");
// do stuff here
[login release];
}
Recommended Reads:
Send Invitation via Facebook
API Permissions
An Example
NOTE:
1) Don't forget to setup Facebook application ID in plist.
2) Don't forget to adjust AppDelegate to handle urls.
Partial snippet taken from above link in point 2:
/*
* If we have a valid session at the time of openURL call, we handle
* Facebook transitions by passing the url argument to handleOpenURL
*/
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
// attempt to extract a token from the url
return [FBSession.activeSession handleOpenURL:url];
}
Hope it helps!
EDIT
Here:
declaration for fbDelegate is:
#property (nonatomic, assign) id <FBLoginDelegate> fbDelegate;
#protocol FBLoginDelegate <NSObject>
#required
-(void) OnFBSuccess;
-(void) OnFBFailed : (NSError *)error;
#end
and this is how you can consume this code:
FBLoginHandler *login = [[FBLoginHandler alloc] initWithDelegate:self]; // here 'self' is the fbDelegate you have asked about
[login inviteFriends];

I think your application is not enable for Android and for web . And you are trying to get notification on web or on Android device.
Points : For getting notification on Android or on web you have to enable your app for Android and web too .
To Enable Android and Web on your App : GoTo your App > Setting > Click on + Add platform add enter necessary information and Save .
Lets Enjoy Notification . :-)

Related

Snapchat's SnapKit addLoginStatusObserver not listening to events

I'm currently developing a React Native plugin for Snapchat's SnapKit SDK.
I can't seem to get the addLoginStatusObserver method to work (detailed here: https://snapkit.com/docs/api/ios/) and I suspect it's my lack of experience with Objective C's protocol/interface/implementation features.
Here's a trimmed down version of the code:
...
#interface RNSnapSDKListener : NSObject<SCSDKLoginStatusObserver> {
...
}
- (void)scsdkLoginLinkDidSucceed;
- (void)scsdkLoginLinkDidFail;
- (void)scsdkLoginDidUnlink;
...
#end
#implementation RNSnapSDKListener
- (void)scsdkLoginLinkDidSucceed{
NSLog(#"[RNSnapSDKListener] Snapchat Did Login!");
}
- (void)scsdkLoginLinkDidFail{
NSLog(#"[RNSnapSDKListener] Snapchat Did Fail!");
}
- (void)scsdkLoginDidUnlink{
NSLog(#"[RNSnapSDKListener] Snapchat Did Unlink!");
}
- (void)setDelegate: (RCTEventEmitter*) eventEmitter{
NSLog(#"[RNSnapSDKListener] Delegate Set!");
}
#end
#implementation RNSnapSDK
...
RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(initialize){
RNSnapSDKListener *listener = [[RNSnapSDKListener alloc] init];
[listener setDelegate:self];
[SCSDKLoginClient addLoginStatusObserver:listener];
}
RCT_EXPORT_METHOD(login)
{
[SCSDKLoginClient loginFromViewController:[UIApplication sharedApplication].delegate.window.rootViewController completion:^(BOOL success, NSError * _Nullable error) {
}];
}
RCT_EXPORT_METHOD(logout: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
[SCSDKLoginClient unlinkAllSessionsWithCompletion:^(BOOL success) {
NSLog(#"Logout %s", success ? "true" : "false");
resolve(NULL);
}];
}
RCT_EXPORT_METHOD(getUserData: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
NSString *graphQLQuery = #"{me{externalId, displayName, bitmoji{avatar}}}";
NSDictionary *variables = #{#"page": #"bitmoji"};
[SCSDKLoginClient fetchUserDataWithQuery:graphQLQuery
variables:variables
success:^(NSDictionary *resources) {
NSDictionary *data = resources[#"data"];
resolve(data);
} failure:^(NSError * error, BOOL isUserLoggedOut) {
NSLog(#"%#",[error localizedDescription]);
NSLog(#" %s", isUserLoggedOut ? "true" : "false");
if(isUserLoggedOut){
[SCSDKLoginClient loginFromViewController:[UIApplication sharedApplication].delegate.window.rootViewController completion:^(BOOL success, NSError * _Nullable error) {
}];
}else{
reject(#"error", [error localizedDescription], error);
}
}];
}
NSURL *saved;
RCT_EXPORT_METHOD(authenticateDeepLink: (NSString *)url)
{
NSURL *finalUrl = [NSURL URLWithString:url];
saved = finalUrl;
[SCSDKLoginClient application:[UIApplication sharedApplication] openURL:finalUrl options:[NSMutableDictionary dictionary]];
}
...
#end
.initialize() is called inside the React Native module, and the setDelegate() method is called successfully (printing out "Delegate set" - this is for the react-native event bridge), but the other [RNSnapSDKListener]s dont print when they should (after logging in or logging out)
Is this something I'm doing wrong with objective-c or some other misuse of Snapchat's SDK?
Thanks!
The problem ended up being that the RNSnapSDKListener *listener needed to be declared as a global variable and initialized inside initialize() - not entirely sure why though - something with garbage collection maybe?

NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier : Download stops after the app is pushed into background

This method sets the background object.
- (void) downloadWithURL: (NSMutableArray *)urlArray
pathArr: (NSMutableArray *)pathArr
mediaInfo: (MediaInfo *)mInfo
{
bgDownloadMediaInfo = mInfo;
reqUrlCount = urlArray.count;
dict = [NSDictionary dictionaryWithObjects:pathArr
forKeys:urlArray];
mutableDictionary = [dict mutableCopy];
backgroundConfigurationObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:#"XXXXX"];
backgroundConfigurationObject.sessionSendsLaunchEvents = YES;
backgroundConfigurationObject.discretionary = YES;
backgroundSession = [NSURLSession sessionWithConfiguration: backgroundConfigurationObject
delegate: self delegateQueue: [NSOperationQueue currentQueue]];
self.requestUrl = [urlArray objectAtIndex:0];
download = [backgroundSession downloadTaskWithURL:self.requestUrl];
[download resume];
}
These are the completion handlers.
#pragma Mark - NSURLSessionDownloadDelegate
- (void)URLSession: (NSURLSession *)session
downloadTask: (NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL: (NSURL *)location
{
LogDebug(#"Download complete for request url (%#)", downloadTask.currentRequest.URL);
NSString *temp = [mutableDictionary objectForKey:downloadTask.currentRequest.URL];
NSString *localPath = [NSString stringWithFormat: #"%#",temp];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *destinationURL = [NSURL fileURLWithPath: localPath];
NSError *error = nil;
[fileManager moveItemAtURL:location toURL:destinationURL error:&error];
LogDebug(#"Moving download file at url : (%#) to : (%#)", downloadTask.currentRequest.URL, destinationURL);
reqUrlCount --;
downloadSegment ++;
// Handover remaining download requests to the OS
if ([finalUrlArr count] != 0) {
// remove the request from the array that got downloaded.
[finalUrlArr removeObjectAtIndex:0];
[finalPathArr removeObjectAtIndex:0];
if ([finalUrlArr count] > 0) {
// proceed with the next request on top.
self.requestUrl = [finalUrlArr objectAtIndex:0];
download = [backgroundSession downloadTaskWithURL:self.requestUrl];
[download resume];
}
}
if ([adsArray count] > 0) {
adsArrayCount --;
// delegate back once all the ADs segments have been downloaded.
if (adsArrayCount == 0) {
for (int i = 0; i < [adsArray count]; i++) {
NSArray *ads = [adsArray objectAtIndex: i];
for (int j = 0; j < [ads count]; j++) {
MediaInfo *ad = [ads objectAtIndex: j];
[self setDownloadComplete: ad];
// skip sending downloadFinish delegate if the media is marked as downloadDone
if (!ad.downloadDone) {
[delegate MediaDownloadDidFinish: ad.mediaId error: NO];
}
ad.downloadDone = YES;
}
}
downloadSegment = 0;
}
}
// delegate back once all the main media segments have been downloaded.
if (reqUrlCount == 0) {
[self setDownloadComplete: mediaInfo];
state = DownloadState_Done;
// skip sending downloadFinish delegate if the media is marked as downloadDone
if (!bgDownloadMediaInfo.downloadDone) {
[delegate MediaDownloadDidFinish: bgDownloadMediaInfo.mediaId error: NO];
}
bgDownloadMediaInfo.downloadDone = YES;
[urlArr release];
[pathArr release];
[finalUrlArr release];
[finalPathArr release];
// invalidate the NSURL session once complete
[backgroundSession invalidateAndCancel];
}
}
- (void)URLSession: (NSURLSession *)session
task: (NSURLSessionTask *)task
didCompleteWithError: (NSError *)error
{
if (error) {
NSLog(#"Failure to download request url (%#) with error (%#)", task.originalRequest.URL, error);
}
}
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
// save the total downloaded size
[self downloaderDidReceiveData:bytesWritten];
// enable the log only for debugging purpose.
// LogDebug(#"totalBytesExpectedToWrite %llu, totalBytesWritten %llu, %#", totalBytesExpectedToWrite, totalBytesWritten, downloadTask.currentRequest.URL);
}
With out this code(beginBackgroundTaskWithExpirationHandler) the download stops when the app is pushed into background.
// AppDelegate_Phone.m
- (void)applicationDidEnterBackground: (UIApplication *)application
{
NSLog(#"applicationDidEnterBackground");
UIApplication *app = [UIApplication sharedApplication];
UIBackgroundTaskIdentifier bgTask;
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
}];
}
Have you implemented application:handleEventsForBackgroundURLSession:completionHa‌​ndler: in your app delegate? That should save the completion handler and start background session with the specified identifier.
If you don't implement that method, your app will not be informed if the download finishes after the app has been suspended (or subsequently terminated in the course of normal app lifecycle). As a result, it might look like the download didn't finish, even though it did.
(As an aside, note that if the user force-quits the app, that not only terminates the download, but obviously won't inform your app that the download was terminated until the user manually restarts the app at some later point and your app re-instantiates the background session. This is a second-order concern that you might not worry about until you get the main background processing working, but it's something to be aware of.)
Also, your URLSessionDidFinishEventsForBackgroundURLSession: must call that saved completion handler (and dispatch this to the main queue).
Also, your design looks like it will issue only one request at a time. (I'd advise against that, but let's just assume it is as you've outlined above.) So, let's imagine that you have issued the first request and the app is suspended before it's done. Then, when the download is done, the app is restarted in the background and handleEventsForBackgroundURLSession is called. Let's assume you fixed that to make sure it restarts the background session so that the various delegate methods can be called. Make sure that when you issue that second request for the second download that you use the existing background session, not instantiating a new one. You can have only one background session per identifier. Bottom line, the instantiation of the background session should be decoupled from downloadWithURL:pathArr:mediaInfo:. Only instantiate a background session once.
Add "Required background modes" in your .plist
There, add the item "App downloads content from the network"

openActiveSessionWithReadPermissions does not log in automatically

I have recently started using Facebook SDK 3.1, and am encountering some problems with logging in using openActiveSessionWithReadPermissions.
Actually, loggin in works perfectly, if there is a cached token available, it logs in without presenting Facebook UI, and if not, it presents the UI.
The problem occurs after I make a call to reauthorizeWithPublishPermissions. If I call reauthorizeWithPublishPermissions, then close and reopen the application, and make a call to openActiveSessionWithReadPermissions, it presents the Facebook UI and requires the user to say "yes I'm OK with read permissions", even though there is a cached token available.
It only presents the Facebook UI erroneously if I make a call to reauthorizeWithPublishPermissions, otherwise everything works fine.
Open for read code:
[FBSession openActiveSessionWithReadPermissions:readpermissions allowLoginUI:YES
completionHandler:^(FBSession *aSession, FBSessionState status, NSError *error) {
[self sessionStateChanged:[FBSession activeSession] state:status error:error];
if (status != FBSessionStateOpenTokenExtended) {
// and here we make sure to update our UX according to the new session state
FBRequest *me = [[FBRequest alloc] initWithSession:aSession
graphPath:#"me"];
[me startWithCompletionHandler:^(FBRequestConnection *connection,
NSDictionary<FBGraphUser> *aUser,
NSError *error) {
self.user = aUser;
aCompletionBlock(aSession, status, error);
}];
}
}];
the sessionStateChanged function:
- (void)sessionStateChanged:(FBSession *)aSession state:(FBSessionState)state error:(NSError *)error {
if (aSession.isOpen) {
// Initiate a Facebook instance and properties
if (nil == self.facebook || state == FBSessionStateOpenTokenExtended) {
self.facebook = [[Facebook alloc]
initWithAppId:FBSession.activeSession.appID
andDelegate:nil];
// Store the Facebook session information
self.facebook.accessToken = FBSession.activeSession.accessToken;
self.facebook.expirationDate = FBSession.activeSession.expirationDate;
}
} else {
// Clear out the Facebook instance
if (state == FBSessionStateClosedLoginFailed) {
[FBSession.activeSession closeAndClearTokenInformation];
}
self.facebook = nil;
}
}
the Publish call, with an empty aPublishAction for testing:
- (void)doPublishAction:(void(^)(FBSession *aSession, NSError *error))aPublishAction {
if ([FBSession.activeSession.permissions
indexOfObject:#"publish_actions"] == NSNotFound) {
NSArray *writepermissions = [[NSArray alloc] initWithObjects:
#"publish_stream",
#"publish_actions",
nil];
[[FBSession activeSession]reauthorizeWithPublishPermissions:writepermissions defaultAudience:FBSessionDefaultAudienceFriends completionHandler:^(FBSession *aSession, NSError *error){
if (error) {
NSLog(#"Error on public permissions: %#", error);
}
else {
aPublishAction(aSession, error);
}
}];
}
else {
// If permissions present, publish the story
aPublishAction(FBSession.activeSession, nil);
}
}
Thanks for everything in advance, I would be grateful for any and all help!!
You need to add
[FBSession.activeSession handleDidBecomeActive];
to your -(void) applicationDidBecomeActive:(UIApplication *)application method in your app's delegate as stated in the migration guide.
What if you use openActiveSessionWithPermissions instead of openActiveSessionWithReadPermissions?

Google Plus iOS SDK: how to get logged in user email?

Currently playing around with GooglePlusSample with scope:
#"https://www.googleapis.com/auth/plus.me",
#"https://www.googleapis.com/auth/userinfo.email" and
#"https://www.googleapis.com/auth/userinfo.profile".
Tried calling auth.userEmail, auth.userData in callback method finishedWithAuth:error:, but both are empty...
-(void)finishedWithAuth:(GTMOAuth2Authentication *)auth error:(NSError *)error{
NSLog(#"Received Error %# and auth object==%#",error,auth);
if (error) {
// Do some error handling here.
} else {
[self refreshInterfaceBasedOnSignIn];
NSLog(#"email %# ",[NSString stringWithFormat:#"Email: %#",[GPPSignIn sharedInstance].authentication.userEmail]);
NSLog(#"Received error %# and auth object %#",error, auth);
// 1. Create a |GTLServicePlus| instance to send a request to Google+.
GTLServicePlus* plusService = [[GTLServicePlus alloc] init] ;
plusService.retryEnabled = YES;
// 2. Set a valid |GTMOAuth2Authentication| object as the authorizer.
[plusService setAuthorizer:[GPPSignIn sharedInstance].authentication];
GTLQueryPlus *query = [GTLQueryPlus queryForPeopleGetWithUserId:#"me"];
// *4. Use the "v1" version of the Google+ API.*
plusService.apiVersion = #"v1";
[plusService executeQuery:query
completionHandler:^(GTLServiceTicket *ticket,
GTLPlusPerson *person,
NSError *error) {
if (error) {
//Handle Error
} else
{
NSLog(#"Email= %#",[GPPSignIn sharedInstance].authentication.userEmail);
NSLog(#"GoogleID=%#",person.identifier);
NSLog(#"User Name=%#",[person.name.givenName stringByAppendingFormat:#" %#",person.name.familyName]);
NSLog(#"Gender=%#",person.gender);
}
}];
}
}
Once user is authenticated you can call [[GPPSignIn sharedInstance] userEmail] to get authenticated user's email.
This worked for me :
Firstly use the userinfo.email scope as per :
signInButton.scope = [NSArray arrayWithObjects:
kGTLAuthScopePlusMe,
kGTLAuthScopePlusUserinfoEmail,
nil];
Then define these methods :
- (GTLServicePlus *)plusService {
static GTLServicePlus* service = nil;
if (!service) {
service = [[GTLServicePlus alloc] init];
// Have the service object set tickets to retry temporary error conditions
// automatically
service.retryEnabled = YES;
// Have the service object set tickets to automatically fetch additional
// pages of feeds when the feed's maxResult value is less than the number
// of items in the feed
service.shouldFetchNextPages = YES;
}
return service;
}
- (void)fetchUserProfile {
// Make a batch for fetching both the user's profile and the activity feed
GTLQueryPlus *profileQuery = [GTLQueryPlus queryForPeopleGetWithUserId:#"me"];
profileQuery.fields = #"id,emails,image,name,displayName";
profileQuery.completionBlock = ^(GTLServiceTicket *ticket, id object, NSError *error) {
if (error == nil) {
// Get the user profile
GTLPlusPerson *userProfile = object;
// Get what we want
NSArray * userEmails = userProfile.emails;
NSString * email = ((GTLPlusPersonEmailsItem *)[userEmails objectAtIndex:0]).value;
NSString * name = userProfile.displayName;
NSString * profileId = userProfile.identifier;
} else {
// Log the error
NSLog(#"Error : %#", [error localizedDescription]);
}
};
GTLBatchQuery *batchQuery = [GTLBatchQuery batchQuery];
[batchQuery addQuery:profileQuery];
GTLServicePlus *service = self.plusService;
self.profileTicket = [service executeQuery:batchQuery
completionHandler:^(GTLServiceTicket *ticket,
id result, NSError *error) {
self.profileTicket = nil;
// Update profile
}];
}
And finally call the "fetchUserProfile" method in the "finishedWithAuth" as per :
- (void)finishedWithAuth: (GTMOAuth2Authentication *)auth
error: (NSError *) error
{
// An error?
if (error != nil) {
// Log
} else {
// Set auth into the app delegate
myAppDelegate *appDelegate = (myAppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.auth = auth;
// Get user profile
self.plusService.authorizer = auth;
[self fetchUserProfile];
}
}
Note this may not be perfect as it's a 'work in progress', in particular re: getting the correct email address when the user has more than one but it's a start!
Good luck.
Steve
If you have Access not configured error check services in google api console. make sure you enable google plus api services.

FBConnect iOS SDK publish_action Issue

I must say I feel like an idiot right now. :) I've been up and down Facebook, Google and StackOverflow and still cannot get the answer to what I am doing wrong! :) I've looked at both Facebook examples: Hackbook and WishList. Wishlist is obviously the one that should tell me what to do, but ALL the examples I've seen have the OBJECT part as a URL. I do not necessarily desire this, as I just want the post to say (this user) is playing [MyGame].
Ok here's my goal. I have an iPhone game. I want to do what Spotify does when you listen to a song, which posts to timeline and ticker. I'd also like to use this to post the player's score on the user's timeline and ticker.
I setup Open Graph with an action called Play and an object called Game along with it's aggregator. I think I also will need an action called Score?
Anyways, I can successfully post to a user's wall using feed dialog, but that's not what I want for the Play action.
Here's a concise version of what I got so far, any help is much appreciated:
Couple Notes:
I have a singleton FacebookInfo which takes care of handling Facebook delegates and stuff. I also have a FacebookUser class which hold the current user's info for the current session, populated when calling me. I also have a DLog method which simply does an NSlog only on debug mode.
When a user clicks the Play button in my game, I would like to call my method [[Facebook sharedInfo] publishAction:#"play"] below. I'm passing an NSString as action so later I can call the same method and use an action like Score and just modify the post accordingly.
#interface FacebookInfo : NSObject {
Facebook *_facebook;
FacebookUser *_facebookUser;
}
#property (nonatomic, retain) Facebook *facebook;
#property (nonatomic, retain) FacebookUser *facebookUser;
+(id)sharedInfo;
-(BOOL)isFacebookAuthenticated;
-(void)fbDidLogout;
-(void)getMe;
-(void)publishFeed;
-(void)publishWithAction:(NSString *)action;
#end
static FacebookInfo *facebookInfo = nil;
#implementation FacebookInfo
#synthesize facebook = _facebook;
#synthesize facebookUser = _facebookUser;
#pragma mark - Custom Methods
-(void)getMe {
DLog(#"**********");
/* when forcing FBConnect to show inline dialog instead of using SSO, this works.
apparently this fails when using SSO, error:
Err message: (null)
Err code: 10000
*/
[[self facebook] requestWithGraphPath:#"me" andDelegate:self];
}
-(void)publishWithAction:(NSString *)action {
DLog(#"**********");
if ([action isEqualToString:#"play"]) {
// Build the params list
NSMutableDictionary *params = [[NSMutableDictionary alloc] initWithCapacity:1];
// all sample have this pointing to a URL. Do i really need to do that?
[params setValue:kFBAppNameSpace forKey:#"game"];
// I know I may need more parameters, but which are required?
// Do I need to add the one's that Facebook Javascript examples have,
// like title, description? I think it's here where I'm mostly confused.
// Make the Graph API call to add to the wishlist
[[self facebook] requestWithGraphPath:#"me/[myappnamespace]:play"
andParams:params
andHttpMethod:#"POST"
andDelegate:self];
[params release];
}
}
-(void)publishFeed {
DLog(#"**********");
/*
This works perfectly fine
*/
SBJSON *jsonWriter = [[SBJSON new] autorelease];
NSDictionary *actionLinks = [NSArray arrayWithObjects:
[NSDictionary dictionaryWithObjectsAndKeys:
#"Get The App",#"name",
kFBAppURL,#"link",
nil],
nil];
NSString *actionLinksStr = [jsonWriter stringWithObject:actionLinks];
NSString *app_id = kFBAppID;
NSString *user_message_prompt = [NSString stringWithFormat:#"Post to Your Wall!"];
NSString *name = [NSString stringWithFormat:#"[MyGameName]"];
NSString *caption = [NSString stringWithFormat:#"%# has gotten a score of %#!",[[self facebookUser] firstName],[[[GameInfo sharedGameInfo] scoreTotal] stringValue]];
NSString *description = [NSString stringWithFormat:#"Can you beat this score?!"];
NSString *link = kFBAppURL;
NSString *picture = kFBAppImage;
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
app_id, #"app_id",
user_message_prompt, #"user_message_prompt",
name, #"name",
caption, #"caption",
description, #"description",
link, #"link",
picture, #"picture",
actionLinksStr, #"actions",
nil];
[[self facebook] dialog:#"feed"
andParams:params
andDelegate:self];
}
-(BOOL)checkForPreviousAccessToken {
DLog(#"**********");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"FBAccessTokenKey"] && [defaults objectForKey:#"FBExpirationDateKey"]) {
DLog(#"FB: Token Exists!");
[[self facebook] setAccessToken:[defaults objectForKey:#"FBAccessTokenKey"]];
[[self facebook] setExpirationDate:[defaults objectForKey:#"FBExpirationDateKey"]];
}
if (![[self facebook] isSessionValid]) {
DLog(#"FB: Authorizing...");
NSArray *permissions = [[NSArray alloc] initWithObjects:
#"publish_stream",
#"publish_actions",
#"offline_access",
nil];
[[self facebook] authorize:permissions];
[permissions release];
} else {
DLog(#"FB: Authorized!!!");
// show logged in
[self getMe];
}
return [[self facebook] isSessionValid];
}
-(BOOL)isFacebookAuthenticated {
DLog(#"**********");
return [self checkForPreviousAccessToken];
}
-(void)extendAccessTokenIfNeeded {
DLog(#"**********");
[[self facebook] extendAccessTokenIfNeeded];
[[FacebookInfo sharedInfo] getMe];
}
-(void)logout {
DLog(#"**********");
[[self facebook] logout:self];
}
#pragma mark - FBConnect Delegate Methods
-(void)fbDidLogin {
DLog(#"**********");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[[self facebook] accessToken] forKey:#"FBAccessTokenKey"];
[defaults setObject:[[self facebook] expirationDate] forKey:#"FBExpirationDateKey"];
[defaults synchronize];
[self getMe];
}
- (void)fbDidNotLogin:(BOOL)cancelled {
DLog(#"**********");
}
- (void)fbDidExtendToken:(NSString*)accessToken expiresAt:(NSDate*)expiresAt {
DLog(#"**********");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:accessToken forKey:#"FBAccessTokenKey"];
[defaults setObject:expiresAt forKey:#"FBExpirationDateKey"];
[defaults synchronize];
}
-(void)fbDidLogout {
DLog(#"**********");
// Remove saved authorization information if it exists
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"FBAccessTokenKey"]) {
[defaults removeObjectForKey:#"FBAccessTokenKey"];
[defaults removeObjectForKey:#"FBExpirationDateKey"];
[defaults synchronize];
}
}
- (void)fbSessionInvalidated {
DLog(#"**********");
}
#pragma mark - FBRequestDelegate Methods
/**
* Called when the Facebook API request has returned a response. This callback
* gives you access to the raw response. It's called before
* (void)request:(FBRequest *)request didLoad:(id)result,
* which is passed the parsed response object.
*/
- (void)request:(FBRequest *)request didReceiveResponse:(NSURLResponse *)response {
DLog(#"**********");
//DLog(#"received response");
}
/**
* Called when a request returns and its response has been parsed into
* an object. The resulting object may be a dictionary, an array, a string,
* or a number, depending on the format of the API response. If you need access
* to the raw response, use:
*
* (void)request:(FBRequest *)request
* didReceiveResponse:(NSURLResponse *)response
*/
- (void)request:(FBRequest *)request didLoad:(id)result {
DLog(#"**********");
//code removed for this example
}
/**
* Called when an error prevents the Facebook API request from completing
* successfully.
*/
- (void)request:(FBRequest *)request didFailWithError:(NSError *)error {
DLog(#"**********");
DLog(#"Err message: %#", [[error userInfo] objectForKey:#"error_msg"]);
DLog(#"Err code: %d", [error code]);
if ([error code] == 190) {
// logout
} else {
DLog(#"There was an error making your request.");
}
}
#pragma mark - Singleton Methods
+ (id)sharedInfo {
//DLog(#"**********");
#synchronized(self) {
if(facebookInfo == nil)
facebookInfo = [[super allocWithZone:NULL] init];
}
return facebookInfo;
}
+ (id)allocWithZone:(NSZone *)zone {
DLog(#"**********");
return [[self sharedInfo] retain];
}
- (id)copyWithZone:(NSZone *)zone {
DLog(#"**********");
return self;
}
- (id)retain {
DLog(#"**********");
return self;
}
- (unsigned)retainCount {
DLog(#"**********");
return UINT_MAX; //denotes an object that cannot be released
}
- (oneway void)release {
DLog(#"**********");
// never release
}
- (id)autorelease {
DLog(#"**********");
return self;
}
- (id)init {
DLog(#"**********");
if ((self = [super init]) != NULL) {
//Init
[self setFacebook:[[[Facebook alloc] initWithAppId:kFBAppID urlSchemeSuffix:kFBUrlSchemeSuffix andDelegate:self] autorelease]];
[self setFacebookUser:[[[FacebookUser alloc] init] autorelease]];
}
return self;
}
- (void)dealloc {
DLog(#"**********");
// Should never be called, but just here for clarity really.
DLog(#"Release FacebookInfo...");
[super dealloc];
}
#end
#implementation AppDelegate_iPhone
// Add for Facebook SSO support (4.2+)
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
[[[FacebookInfo sharedInfo] facebook] handleOpenURL:url];
}
// Add for Facebook SSO support (pre 4.2)
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
[[[FacebookInfo sharedInfo] facebook] handleOpenURL:url];
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
DLog(#"**********");
[[FacebookInfo sharedInfo] extendAccessTokenIfNeeded];
}
Wow, I know this is a long post, but I hope someone can help point me in the right direction.
UPDATE 1: (2012/18/02)
Ok. So I settled for making a page which contains the og meta-data which I did not want to do and supplied the url for my object. As per the FB Documentation, found here:
Open Graph Mechanics
When users take an action in your app, such as cook the Stuffed Cookie, the app calls a Graph API to create a new cook action that connects the user with the Stuffed Cookie object. This is accomplished by issuing a HTTP POST to the user’s /me/myapp:cook connection with the URL of the recipe object. Facebook will then crawl the object URL, read the metadata, and connect the object to user's Graph via the action.
The diagram below illustrates the process:
User takes an action in the app, such as "cook" a "recipe"
App calls a Graph API /me/action:object=Object_URL
Facebook will crawl the object URL, read its meta tags and connects the object to the user's Graph via the action.
It would be cool if we can define these things in the app itself as params for cases where I do not need a website for.
As of 4/21/12 Facebook requires you to have created a page to get the data desired.