Part of building a robust and reliable app is being able to properly respond to errors. The Facebook SDK provides features that enable you to respond appropriately to errors that users may encounter when using your app.
Scenarios that you should detect and handle include:
Depending on the error you encounter, Facebook recommends certain recovery tactics. The Facebook SDK provides error info that you can process to get the error code, error message, optional error subcode, and any recommended recovery tactic. The Graph API Errors provides more details on the possible error responses from the Facebook server that the Facebook SDK then processes.
The FBError
category on NSError
adds properties used to help you detect and recover from Facebook-related errors. Handling errors typically involves this flow:
The fberrorShouldNotifyUser
property should first be checked and if YES, your app should display the associated fberrorUserMessage
property. If fberrorShouldNotifyUser
is NO, then fberrorCategory
provides classification info that should be examined to determine the best course of action. In general, your app should handle errors encountered during authentication, permissions request and Graph API requests. This doc covers:
To use the NSError(FBError)
properties, add the -ObjC
linker flag to your Xcode project:
This ensures that the extended NSError
class is properly loaded.
Your app will receive errors during log in if the user denies your app access or the user has not fully validated their Facebook account. Your app will also encounter errors when the Facebook SDK makes a Graph API request and the user's session is no longer valid. These two types of errors are authentication-related errors.
In these scenarios, you'll add authentication error handling code to callbacks for opening Facebook sessions and for responding to session changes. If you open the session using one of the FBSession
class open*Handler:
methods, then implement the handler to check for errors. If you open the session using FBLoginView
, then implement the loginView:handleError:
method of the FBLoginViewDelegate
protocol to add your error logic. If you open the session using FBUserSettingsViewController
, then implement the loginViewController:receivedError:
method of the FBUserSettingsDelegate
protocol to add your error logic.
Your error handling logic should expect an NSError(FBError)
object with properties you can examine to handle Facebook-related errors. The fberrorShouldNotifyUser
property returns YES for cases such as a Facebook password change requiring an iOS6 settings password update or the user disallowing your app from accessing their Facebook account in iOS6 settings. In these scenarios, the fberrorUserMessage
property should be echoed back to the user. You can think of this as a flow that requires an out-of-band action by the user to recover from the error.
Here are common fberrorCategory
values that may be encountered:
Error classification | Reason | Recovery tactic |
---|---|---|
FBErrorCategoryUserCancelled |
The user denies permissions to your app. | Do nothing. |
FBErrorCategoryAuthenticationReopenSession |
An authentication error has occurred and the session should be reopened. | Display a message asking the user to log in again. |
All others | Various. | Show a generic message to the user. |
The next subsections show you how to handle the two types of authentication-related errors.
Possible errors during Facebook Login include:
As an example, here's a flow in the Scrumptious sample app that occurs when a previously authenticated user turns off the app's permissions in iOS6 settings and then tries to log in again:
This is an example that requires an out-of-band user action to recover from the error. The fberrorShouldNotifyUser
property is therefore set to YES and the fberrorUserMessage
message provides the message that is then displayed to the user. In this scenario, the user is instructed to verify their Facebook account app settings.
The error recovery tactic may not require an out-of-band action. In these scenarios, the fberrorShouldNotifyUser
property returns NO and you need to examine the fberrorCategory
property to best handle the error.
As an example, here's a flow in the Scrumptious sample app that occurs when the user does not grant the app permissions:
In this scenario, the event is simply logged to the console:
2013-02-12 14:50:36.033 Scrumptious[60364:c07] user cancelled login
Here's sample code you can use to handle Facebook Login errors:
- (void)handleAuthError:(NSError *)error{
NSString *alertMessage, *alertTitle;
if (error.fberrorShouldNotifyUser) {
// If the SDK has a message for the user, surface it.
alertTitle = @"Something Went Wrong";
alertMessage = error.fberrorUserMessage;
} else if (error.fberrorCategory == FBErrorCategoryUserCancelled) {
// The user has cancelled a login. You can inspect the error
// for more context. For this sample, we will simply ignore it.
NSLog(@"user cancelled login");
} else {
// For simplicity, this sample treats other errors blindly.
alertTitle = @"Unknown Error";
alertMessage = @"Error. Please try again later.";
NSLog(@"Unexpected error:%@", error);
}
if (alertMessage) {
[[[UIAlertView alloc] initWithTitle:alertTitle
message:alertMessage
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil] show];
}
}
If you use FBLoginView
to open a session, implement the loginView:handleError:
delegate method and call the handleAuthError:
method:
- (void)loginView:(FBLoginView *)loginView
handleError:(NSError *)error{
[self handleAuthError:error];
}
If you use the open*Handler:
methods of the FBSession
class to open a session, call the handleAuthError:
method in the handler:
[FBSession openActiveSessionWithReadPermissions:nil
allowLoginUI:YES
completionHandler:^(FBSession *session,
FBSessionState state,
NSError *error) {
if (error) {
// Handle errors
[self handleAuthError:error];
} else {
// No error
...
}
}];
If you use FBUserSettingsViewController
to open a session, implement the loginViewController:receivedError:
delegate method and call the handleAuthError:
method:
- (void)loginViewController:(id)sender
receivedError:(NSError *)error
{
[self handleAuthError:error];
}
A user's session could be invalided due to the following:
This would lead to errors during Facebook Graph API calls.
As mentioned previously, your session open handlers and delegate methods are also called during session state changes. Therefore, in the previous example code, the handleAuthError:
method will be called for Graph API requests that fail due to an invalidated user session. This error results in an error classified as FBErrorCategoryAuthenticationReopenSession
. The current handleAuthError:
method treats this as an unknown error. You can enhance your error logic to properly handle this error classification:
- (void)handleAuthError:(NSError *)error{
NSString *alertMessage, *alertTitle;
if (error.fberrorShouldNotifyUser) {
// If the SDK has a message for the user, surface it.
alertTitle = @"Something Went Wrong";
alertMessage = error.fberrorUserMessage;
} else if (error.fberrorCategory == FBErrorCategoryAuthenticationReopenSession) {
// It is important to handle session closures since they can happen
// outside of the app. You can inspect the error for more context
// but this sample generically notifies the user.
alertTitle = @"Session Error";
alertMessage = @"Your current session is no longer valid. Please log in again.";
} else if (error.fberrorCategory == FBErrorCategoryUserCancelled) {
...
The user would now see an error message similar to this:
You can provide more contextual error messages instead of showing the generic message. You can do this by examining the userInfo
property of the error. See the providing more contextual error section for more details on how to do this.
Your app may need to ask for additional permissions after the user is already authenticated. An example would be if your app publishes to the user's timeline and needs to request publish_actions
permissions the very first time the user chooses to publish a story:
During the new permissions request, the possible errors include:
The error scenarios are a subset of those that happen during an initial authentication flow.
You'll add your error logic to the handler you define for the requestNewPublishPermissions:defaultAudience:completionHandler:
and requestNewReadPermissions:completionHandler:
methods of the FBSession
class.
Handling new permission request errors is similar to handling errors during the initial authentication flow. You should first check fberrorShouldNotifyUser
to see if the error requires the user to be notified. If the user does not need to be notified you can check the fberrorCategory
error classification. If the value of the category is FBErrorCategoryUserCancelled
then the user declined to give your app additional permissions. You can handle any other errors by displaying a generic message to the user.
Here's sample code you can use to handle new permission request errors:
// Helper method to handle errors during permissions request
- (void)handleRequestPermissionError:(NSError *)error
{
if (error.fberrorShouldNotifyUser) {
// If the SDK has a message for the user, surface it.
[[[UIAlertView alloc] initWithTitle:@"Something Went Wrong"
message:error.fberrorUserMessage
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil] show];
} else {
if (error.fberrorCategory == FBErrorCategoryUserCancelled){
// The user has cancelled the request. You can inspect the value and
// inner error for more context. Here we simply ignore it.
NSLog(@"User cancelled post permissions.");
} else {
NSLog(@"Unexpected error requesting permissions:%@", error);
[[[UIAlertView alloc] initWithTitle:@"Permission Error"
message:@"Unable to request publish permissions"
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil] show];
}
}
}
// Method that requests permissions needed to make the API call.
- (void)requestPermissionCallAPI {
[FBSession.activeSession
requestNewPublishPermissions:[NSArray arrayWithObject:@"publish_actions"]
defaultAudience:FBSessionDefaultAudienceEveryone
completionHandler:^(FBSession *session, NSError *error) {
if (error) {
// Handle new permissions request errors
[self handleRequestPermissionError:error];
} else {
// Make API call
...
}
}];
}
The requestPermissionCallAPI
method requests additional permissions by calling the requestNewPublishPermissions:defaultAudience:completionHandler:
method. A handler is defined to check for an error. If an error occurs, the handleRequestPermissionError:
method is called. This is the method that first checks for an out-of-band error to display to the user. Then it checks to see if the user declined permissions. It handles all other errors by displaying a generic message.
Where it makes sense for your app, you can provide a more contextual error message instead of showing the generic message. You can do this by looking for other error classifications or looking into the userInfo
property of the error.
Your app may encounter errors when making a Graph API call. The authentication-related Graph API call errors were discussed previously. Those errors are typically taken care of by FBSession
class open*Handler:
method handlers, by FBLoginViewDelegate
error methods or by FBUserSettingsDelegate
error methods. In this section, we cover Graph API errors that are not authentication-related.
The possible errors include:
You'll add your error logic to the handler corresponding to your Graph API call. For example, the startForMeWithCompletionHandler:
method in the FBRequestConnection
class has a handler
parameter where you can add your error logic. Your error logic should first check fberrorShouldNotifyUser
to see if the error requires the user to be notified. If the user does not need to be notified, check the fberrorCategory
error classification to determine the recovery tactic. Here are common fberrorCategory
values that may be encountered during Graph API requests:
Error classification | Reason | Recovery tactic |
---|---|---|
FBErrorCategoryPermissions |
The user has removed a required permission that was previously granted. | Ask for the permission once more. |
FBErrorCategoryRetry |
There could be a server-side problem. | Retry the operation a certain number of times. |
FBErrorCategoryThrottling |
Server-side throttling. | Employ a back-off period and retry the operation a certain number of times. |
All others | Various. | Show a generic message to the user. |
In the Scrumptious sample app, if the user removes the publish_actions
permissions after previously granting it, the app logs the event and asks for the permission once more:
2013-02-12 15:58:56.540 Scrumptious[61080:c07] Re-requesting permissions
Here's sample code you can use to handle Graph API errors:
// Helper method to handle errors during API calls
- (void)handleAPICallError:(NSError *)error
{
// Some Graph API errors are retriable. For this sample, we will have a simple
// retry policy of one additional attempt.
retryCount++;
if (error.fberrorCategory == FBErrorCategoryRetry ||
error.fberrorCategory == FBErrorCategoryThrottling) {
// We also retry on a throttling error message. A more sophisticated app
// should consider a back-off period.
if (retryCount < 2) {
NSLog(@"Retrying open graph post");
// Recovery tactic: Call API again.
[self makeAPICall];
return;
} else {
NSLog(@"Retry count exceeded.");
}
}
// Users can revoke post permissions on your app externally so it
// can be worthwhile to request for permissions again at the point
// that they are needed. This sample assumes a simple policy
// of re-requesting permissions.
if (error.fberrorCategory == FBErrorCategoryPermissions) {
NSLog(@"Re-requesting permissions");
// Recovery tactic: Ask for required permissions.
[self requestPermissionCallAPI];
return;
}
NSString *alertTitle, *alertMessage;
if (error.fberrorShouldNotifyUser) {
// If the SDK has a message for the user, surface it.
alertTitle = @"Something Went Wrong";
alertMessage = error.fberrorUserMessage;
} else {
NSLog(@"Unexpected error posting to open graph: %@", error);
alertTitle = @"Unknown error";
alertMessage = @"Unable to post to open graph. Please try again later.";
}
if (alertMessage) {
[[[UIAlertView alloc] initWithTitle:alertTitle
message:alertMessage
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil] show];
}
}
// Example API call
- (void)makeAPICall {
static int retryCount = 0;
// FBRequestConnection example API call to me/feed
[FBRequestConnection
startWithGraphPath:@"me/feed"
parameters:@{@"message" : @"Hello world"}
HTTPMethod:@"POST"
completionHandler:^(FBRequestConnection *connection,
id result,
NSError *error) {
// Completion handler block
if (error) {
[self handleAPICallError:error];
} else {
retryCount = 0;
}
}];
}
You can customize how you handle errors beyond what's been recommended or what you've seen in the code samples. You can do this by inspecting the userInfo
dictionary in the NSError
object. The dictionary provides additional reasons for a given error and in some cases provide error subcodes you can use. You'll see some example of how to do this.
One recommendation you've seen is to echo the fberrorUserMessage
message to the user when the fberrorShouldNotifyUser
property is set to YES. The following example is from the authentication error handler sample:
- (void)handleAuthError:(NSError *)error{
...
if (error.fberrorShouldNotifyUser) {
// If the SDK has a message for the user, surface it. This conveniently
// handles cases like password change or iOS6 app slider state.
alertTitle = @"Something Went Wrong";
alertMessage = error.fberrorUserMessage;
...
}
However, you may choose to handle the error in a different way and not show the fberrorUserMessage
message. One scenario is when the user turns off their Facebook account settings. Instead of showing the given message, you could provide your own. You can make this decision by inspecting the userInfo
property in the NSError
object for additional details. For a failed login, the userInfo
dictionary contains a FBErrorLoginFailedReason
key with one of FBErrorLoginFailedReason*
values. See the list of error reasons for more details. Here's an example showing how to do this:
....
if (error.fberrorShouldNotifyUser) {
if ([[error userInfo][FBErrorLoginFailedReason]
isEqualToString:FBErrorLoginFailedReasonSystemDisallowedWithoutErrorValue]) {
// Show a different error message
alertTitle = @"App Disabled";
alertMessage = @"Go to Settings > Facebook and turn ON Scrumptious.";
// Perform any additional customizations
} else {
// If the SDK has a message for the user, surface it.
alertTitle = @"Something Went Wrong";
alertMessage = error.fberrorUserMessage;
}
} else if (error.fberrorCategory == FBErrorCategoryUserCancelled) {
....
The code examines the userInfo
dictionary and checks if the FBErrorLoginFailedReason
key is equal to FBErrorLoginFailedReasonSystemDisallowedWithoutErrorValue
. If there is a match, then login failed due to the Facebook account settings being disabled. In this case, the error message is customized:
Many other code samples showcased generic responses to one error classification by providing a standard message. The following example is from the authentication error handler sample:
- (void)handleAuthError:(NSError *)error{
...
} else if (error.fberrorCategory == FBErrorCategoryAuthenticationReopenSession) {
// It is important to handle session closures since they can happen
// outside of the app. You can inspect the error for more context
// but this sample generically notifies the user.
alertTitle = @"Session Error";
alertMessage = @"Your current session is no longer valid. Please log in again.";
...
}
In this example, the FBErrorCategoryAuthenticationReopenSession
error classification displays the message: "Your current session is no longer valid. Please log in again." You may wish to show the user a more contextual message. Instead of simply showing the "Your current session is no longer valid..." message, you may want to let the user know they're getting the error because they removed your app.
You can do this by examining the userInfo
property of the error. This property may contain an error subcode that has more contextual data you can use. In the uninstall example, debugging the error in the Xcode console yields a userInfo
description similar to this:
{
com.facebook.sdk:HTTPStatusCode=400,
com.facebook.sdk:ParsedJSONResponseKey={
body = {
error = {
code = 190;
"error_subcode" = 458;
message = "Error validating access token: User 1424840234 has not authorized application 233936543368280.";
type = OAuthException;
};
};
code = 400;
}
The debug log shows an error code of 190 and an error subcode of 458. See the Graph API errors doc for what these codes and subcodes mean. The errors doc shows that the user has uninstalled the app and the recommended recovery tactic is to reauthorize the user. The Facebook SDK returns an FBErrorCategoryAuthenticationReopenSession
error classification to match this recommendation.
To provide a contextual error message for the 458 subcode, use the following logic:
...
} else if (error.fberrorCategory == FBErrorCategoryAuthenticationReopenSession) {
NSInteger underlyingSubCode = [[error userInfo]
[@"com.facebook.sdk:ParsedJSONResponseKey"]
[@"body"]
[@"error"]
[@"error_subcode"] integerValue];
if (underlyingSubCode == 458) {
alertTitle = @"Session Error";
alertMessage = @"The app was removed. Please log in again.";
} else {
alertTitle = @"Session Error";
alertMessage = @"Your current session is no longer valid. Please log in again.";
}
}
...
The user now has more context on why they've received the error and sees something similar to this:
You can use the same approach and look at other error codes and subcodes to provide contextual error messages as you see fit.
API Reference:
Samples: