Ruby on Rails, iPhone SDK, .Net, GitHub...

We spend a lot of time thinking about these things. If we have something helpful to share, we'll put it here.

Request a free RaddOnline® proposal.

iPhone SDK: Using Facebook Connect for iPhone, Working with Extended Permissions Part 2 of 2

Posted by Tim Stephenson, RaddOnline® on Sunday, March 22, 2009

Clearing Up UI Confusion

In part one, I describe using the Facebook Connect for iPhone SDK. In the sample application provided by Facebook, a Get Permission button checks to see if the user has granted the appropriate extended permission to the application. It is a permission to post status updates. The permission needs to be granted only once. If the user clicks the button after permission is granted, they see this message: You have already granted this permission to the application. This begs the question, why display the button after permission is granted?

Sample screens shots of the FB Connect App.

I’ll provide a sample project at the end of the article. Facebook updates the SDK frequently, be sure to get the latest version from the Facebook site. Facebook Connect for iPhone

Plan of Action

The documentation describes the process for working with the API, and provides two samples of interest:

- (void)getUserName { 
    NSString* fql = @"select name from user where uid == 1234"; 
    NSDictionary* params = [NSDictionary dictionaryWithObject:fql forKey:@"query"]; 
    [[FBRequest requestWithDelegate:self] call:@"facebook.fql.query" params:params]; 
} 
- (void)request:(FBRequest*)request didLoad:(id)result { 
    NSArray* users = result; 
    NSDictionary* user = [users objectAtIndex:0]; 
    NSString* name = [user objectForKey:@"name"]; 
    NSLog(@"Query returned %@", name); 
}

And

-(void)setUsersStatus { 
    NSString *statusString = exampleTextField.text; 
    NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys: statusString, @"status", @"true", @"status_includes_verb", nil]; 
    [[FBRequest requestWithDelegate:self] call:@"facebook.Users.setStatus" params:params]; 
}

Either works. In the first method, replace the FQL with:

select status_update from permissions where uid == 1234

In the second example, use the Users.hasAppPermission method to confirm if the user has opted into the extended permission. Read more about this method here. Users.hasAppPermission

For my example, I’ve decided to go with the first approach. In either case, use this delegate method to process the request:

 - (void)request:(FBRequest*)request didLoad:(id)result

The SessionViewController in the sample project is using this method to handle the request used to retrieve the user’s name:

- (void)request:(FBRequest*)request didLoad:(id)result {
  NSArray* users = result;
  NSDictionary* user = [users objectAtIndex:0];
  NSString* name = [user objectForKey:@"name"];
  _label.text = [NSString stringWithFormat:@"Logged in as %@", name];
}

To avoid confusion with this code, and to make it easier to reuse in other projects, I’ll create a class with the sole purpose of tracking the update status permission.

Creating the Permission Status Class

Open the project in the fbconnect-iphone/samples/Connect folder by double clicking the Connect.xcodeproj file.

  1. Right click on the Source group and select Add => New File.
  2. Select Cocoa Touch Classes => NSObject subclass and then click Next.
  3. Name the file PermissionStatus.m and be sure to check the Also Create “PermissionStatus.h” check box.
  4. Open the PermissionStatus.h file and make it look like this:
#import <Foundation/Foundation.h>
#import "FBRequest.h" 

@protocol PermissionStatusDelegate;

@interface PermissionStatus : NSObject <FBRequestDelegate> {
    BOOL userHasPermission;
    id<PermissionStatusDelegate> delegate;
}

@property (nonatomic, assign) BOOL userHasPermission;
@property (nonatomic, retain) id<PermissionStatusDelegate> delegate;

- (PermissionStatus *)initWithUserId:(long long int)uid;

@end

#pragma mark method to call after response
@protocol PermissionStatusDelegate <NSObject>
- (void)statusWasSet:(BOOL)status;
@end

There are a few items of interest here.

  1. It imports the FBRequest.h header file.
  2. It implements the FBRequestDelegate protocol.
  3. It defines a protocol of its own, ”@protocol PermissionStatusDelegate;” that can be used to determine the result of the status check.
  4. It contains a property where the delegate can be set.

Next, implement the class as defined in the interface. Open PermissionStatus.m and mimic this:

#import "PermissionStatus.h" 

@implementation PermissionStatus
@synthesize userHasPermission;
@synthesize delegate;

- (PermissionStatus *)initWithUserId:(long long int)uid { 
    self = [super init];

    if (self) {
        NSString* fql = [NSString stringWithFormat:
                         @"select status_update from permissions where uid == %lld", uid]; 
        NSDictionary* params = [NSDictionary dictionaryWithObject:fql forKey:@"query"]; 
        [[FBRequest requestWithDelegate:self] call:@"facebook.fql.query" params:params]; 
        userHasPermission = NO;
    }
    return self;
} 

#pragma mark FBRequestDelegate
- (void)request:(FBRequest*)request didLoad:(id)result {
    NSArray *permissions = result;
    NSDictionary *permission = [permissions objectAtIndex:0];
    userHasPermission = [[permission objectForKey:@"status_update"] boolValue];
    [delegate statusWasSet:userHasPermission];
}

@end

How it works

In the initWithUserId: method, you define the FQL to retrieve the extended permission and add it to the params dictionary. Instantiate the FBRequest with the PermissionStatus instance as the delegate. I set the userHasPermission property to NO just in case an error occurs with the request. I don’t care to report any errors, it returns NO if something went wrong. To report an error, use the request:didFailWithError: method to catch the error.

In the request:didLoad: method I retrieve the result and call our new delegate method statusWasSet: with the value of the response.

Using the PermissionStatus Class to Hide the Get Permission Button.

First, open SessionViewController.h and make these changes:

  1. Import the new class:
    #import "PermissionStatus.h"
  2. Set SessionViewController as a delegate for our class:
    <FBDialogDelegate, FBSessionDelegate, FBRequestDelegate, PermissionStatusDelegate>
  3. Add a property to hold an instance of the PermissionStatus class:
@interface SessionViewController : UIViewController
    <FBDialogDelegate, FBSessionDelegate, FBRequestDelegate, PermissionStatusDelegate> {
        IBOutlet UILabel* _label;
        IBOutlet UIButton* _permissionButton;
        IBOutlet UIButton* _feedButton;
        IBOutlet FBLoginButton* _loginButton;
        FBSession* _session;
        PermissionStatus *permissionStatusForUser;
}

@property(nonatomic,readonly) UILabel* label;
@property (nonatomic, retain) PermissionStatus *permissionStatusForUser;

The Implementation

A few more steps remain to complete the task. Open the SessionViewController.m file and make these changes:

Synthesize the property to hold the instance of PermissionStatus

@synthesize permissionStatusForUser;

Use the dialogDidSucceed: method in the FBDialogDelegate protocol to hide the button if the permission dialog succeeds.

- (void)dialogDidSucceed:(FBDialog*)dialog { 
    _permissionButton.hidden = YES;
}

Look for the FBSessionDelegate section and instantiate the PermissionStatus class in the session:didLogin: method

// FBSessionDelegate

- (void)session:(FBSession*)session didLogin:(FBUID)uid {
    //_permissionButton.hidden = NO;
    _feedButton.hidden = NO;

    NSString* fql = [NSString stringWithFormat:
        @"select uid,name from user where uid == %lld", session.uid];

    NSDictionary* params = [NSDictionary dictionaryWithObject:fql forKey:@"query"];
    [[FBRequest requestWithDelegate:self] call:@"facebook.fql.query" params:params];

        //Instantiate the PermissionStatus class with the user id.
    permissionStatusForUser = [[PermissionStatus alloc] initWithUserId:session.uid];
    permissionStatusForUser.delegate = self;
}

Implement the statusWasSet: delegate method and set the hidden property of the button to match the status. When done, release the object.

#pragma mark RAD PermissionStatusDelegate
- (void)statusWasSet:(BOOL)status {
    _permissionButton.hidden = status;
    [permissionStatusForUser release];
}

Build and run the application. The Get Permission button hides if permission was previously granted. If not, it remains visible.

Here is the sample file. I’ve removed the API Keys and Template Bundle IDs. Just add the appropriate IDs and enjoy. fbconnect-iphone.zip

kick it on iPhoneKicks.com

Comments

Emanuele said on Monday, April 06, 2009:

Hi,
nice tutorial.
I'm trying to understand if it's possible to avoid using the provided FBLoginDialog for the first connection.

As far as I understood it would be enought to get a token and then use it to get the Session.

Do you have any idea how to avoid connecting passing through the WebView in the FBLoginDialog?

Thanks a lot

shai said on Saturday, May 09, 2009:

Hi, Thanks for the tutorial.

I am trying to create a post with an image to fill a simple template. For the template, I need to enter the image url.

Since my image is a UIImage in memory, I need to upload it to FB before, right ?

Anyway - the problem is that once I do upload it to the photo album, I can't figure out a way to obtain its URL (no in the returned header etc.)

Any ideas how to overcome this ?

Thanks !

David said on Monday, June 15, 2009:

Yep, the FB APIs are half-baked and somewhat paranoid. Whoever had the idea to make the request happen in one place and the response from the request come back in another method
should be dragged out and beaten with penguins. This does not lead towards simple pipelining.

They should have made a complete wrapper for their entire API in Objective-C messages.

Alan said on Wednesday, September 02, 2009:

Thanks for the great tutorial.

Most apps I've seen (including my new game iLander I hope) don't have a separate add permissions button.

The idea seems to be that the app has a button for submitting a score, and when the button is pressed, you are logged in if needed and permissions are requested if needed.

Even if the user has already granted permissions, requesting them again will simply open the popup which will close a second later.

My main issue at the moment is wondering how to maintain some idea of the users logged in state. If the user is logged in then I need to provide a logout button and I can also skip the login dialog (and now thanks to your article the permissions dialog).

I'm guessing that simply saving the result of the resume operation is enough but it doesn't seem that robust to me.

soniccat said on Wednesday, September 30, 2009:

Have crash after login and start application if already logged (Simulator 3.0).

Replace:
FBUID uid = [defaults integerForKey:@"FBUserId"];

to
FBUID uid = [[defaults objectForKey:@"FBUserId"] longLongValue];

Replace:
[[object objectForKey:@"uid"] intValue];

to
[[object objectForKey:@"uid"] longLongValue];

Replace:
[defaults setInteger:_uid forKey:@"FBUserId"];

to
[defaults setObject:[NSNumber numberWithLongLong:_uid] forKey:@"FBUserId"];

and now all work.
Why you modified that facebook code?

Joe said on Wednesday, October 21, 2009:

Greetings! Is anyone having trouble with this, in terms of ending up at sessionDidNotLogin instead, even though you've provided correct credentials, etc.?

I'm seeing this mentioned on the FB developer forum as well. I can't help but think it's pilot error, but I'm even seeing it happen with the _sample_ app.

Please tell me we're allowed to use this SDK with sandboxed/developer-mode apps! Important if an app isn't yet in the store - and your client doesn't want it to be public knowledge. :)

Joe said on Wednesday, October 21, 2009:

Ah-ha! My IP wasn't whitelisted in the app. (I'm not yet using my web server to handle the app secret. Once I do, we'll use that IP instead.)

cesar Augusto Soto Caballero said on Wednesday, November 25, 2009:

hi man.. nice blog.. i have a trouble here. I want to show my friends list on my iphone app but i cant still found information about it. pls gimme a tip! :) thanks a lot man
regards from peru
cesar

edgard said on Saturday, July 17, 2010:

I can not see the action for SessionViewController in IB. How I do this to connect the button Feed and Permission

thanks

Post a comment


(required, but not displayed)