fbconnect logout memory leak - iphone

this is the first time I post a question here. Usually, I found help on web for my Iphone projects problems, but here I am REALLY STUCK !
I use the facebook iphone-sdk to post some infos on a user's wall. Everything works fine. But I have a leak memory when I logout with the fbconnect loginbutton.
Here is the code I used in the implementation file for a test :
- (void)viewDidLoad {
//session facebook
session = [[FBSession sessionForApplication:#"APP_KEY"
secret:#"SECRET_KEY"
delegate:self] retain];
//facebook bouton connect
FBLoginButton *logButton = [[[FBLoginButton alloc] init] autorelease];
[self.view addSubview:logButton];
[super viewDidLoad];
}
- (void) session:(FBSession *) session didLogin:(FBUID) uid {
NSLog(#"login ok");
}
- (void)sessionDidLogout:(FBSession*) session {
NSLog(#"didLogOut called");
}
as you see I did nothing. So when I test this app, I push the connect to facebook buton and I log in without problems.
But when I push the same button, which is labeled now logout, I log out and then just after that a memory leak appears.
In instruments I can find the origin of the problem and it seams that it is the logout method in FBSession.m file who cause this leak. And especially when the unsave method is called from logout method because if I comment the call, the memory leaks does not appear.
So I need help to figure out what causes this.

I am a newbie myself but does putting [super viewDidLoad] on top instead of the bottom help? We are adding your session information to the view and we expect it to be added after all the parent class views are loaded.

Related

Google + iPhone API sign in and share without leaving app

I recently integrated the Google + API in my App, it was a breeze, my only problem with it, is that everything requires you to leave the app and then come back (it uses URL schemes for this). This is not the behavior I would like, is there a way to directly call their services and do whatever I want with the responses just like in LinkedIn API?.
I really want to avoid going back and forth between safari and my app. Any suggestions/documentation is appreciated.
Thank you,
Oscar
UPDATE FROM GOOGLE
today, we released a new Google Sign In iOS SDK with full built-in
support for Sign In via WebView:
developers.google.com/identity/sign-in/ios The SDK supports dispatch
to any of a number of Google apps handling Sign In when present, with
WebView fallback after. In all cases, the Safari switch is avoided,
which we've seen to be the key element in avoiding app rejection.
We're looking forward to getting feedback from people using the new
SDK, and hope its use can replace the (ingenious and diligent)
workarounds people have implemented in the meantime.
THE METHOD BELLOW IS NO LONGER NEEDED
THIS METHOD HANDLES THE LOGIN INTERNAL WITH A CUSTOM UIWebView
THIS WORKS AND WAS APPROVED BY APPLE
My app got kicked from review cause of this
"The app opens a web page in mobile Safari for logging in to Google plus,
then returns the user to the app. The user should be able log in without opening
Safari first."
See this link https://code.google.com/p/google-plus-platform/issues/detail?id=900
I did solved it by following steps
1) create a subclass of UIApplication, which overrides openURL:
.h
#import <UIKit/UIKit.h>
#define ApplicationOpenGoogleAuthNotification #"ApplicationOpenGoogleAuthNotification"
#interface Application : UIApplication
#end
.m
#import "Application.h"
#implementation Application
- (BOOL)openURL:(NSURL*)url {
if ([[url absoluteString] hasPrefix:#"googlechrome-x-callback:"]) {
return NO;
} else if ([[url absoluteString] hasPrefix:#"https://accounts.google.com/o/oauth2/auth"]) {
[[NSNotificationCenter defaultCenter] postNotificationName:ApplicationOpenGoogleAuthNotification object:url];
return NO;
}
return [super openURL:url];
}
#end
this will basically prevent anything to be opened from Chrome on iOS
we catch the auth call and redirect it to our internal UIWebView
2) to info.plist, add the Principal class, and for it Application (or whatever you named the class)
Add plist key "NSPrincipalClass" and as the value the class of your main application (class which extends UIApplication, in this case Application (see code above))
3) catch the notification and open an internal webview
When your custom Application class sends ApplicationOpenGoogleAuthNotification, listen for it somewhere (in the AppDelegate maybe) and when you catch this notification, open a UIWebView (use the URL passed by the notification as the url for the webview) (in my case the LoginViewController listens for this notification and when received, it opens a view controller containing only a webview hooked up to delegate)
4) inside the webview
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if ([[[request URL] absoluteString] hasPrefix:#"com.XXX.XXX:/oauth2callback"]) {
[GPPURLHandler handleURL:url sourceApplication:#"com.google.chrome.ios"n annotation:nil];
// Looks like we did log in (onhand of the url), we are logged in, the Google APi handles the rest
[self.navigationController popViewControllerAnimated:YES];
return NO;
}
return YES;
}
or simmilar code, that handles the response
com.XXX.XXX:/oauth2callback from code above, replace with your company and app identifier, like "com.company.appname:/oauth2callback"
you might want to use #"com.apple.mobilesafari" as sourceApplication parameter
So, it depends what you want to do.
Sign-In: this will always call out to another application. If the Google+ application is installed it will call out to that, else it will fall back to Chrome and Safari.
Sharing/Interactive Posts: right now this always uses Chrome or Mobile Safari.
Retrieving friends, writing app activities, retrieving profile information: All this is done with the access token retrieved after sign in, so does not require leaving the application.
It is possible, though rather unsupported, to skip the SDK and pop up a UIWebView, construct the OAuth link dynamically and send the user to that (take a look at GTMOAuth2ViewControllerTouch in the open source libraries that ship with the SDK). Below is the a very rough example of the kind of thing you could do to plumb it back into the GPPSignIn instance.
However, you would be guaranteeing that the user has to enter their username and password (and maybe 2nd factor). With the Google+ app you're pretty much guaranteed to be already signed in, and with the Chrome/Safari route, there is a chance the user is already signed in (particularly if they're using other apps with Google+ Sign-In).
This also doesn't address sharing, so I would strongly recommend using the existing SDK as far as possible. Filing a feature request for the way you would prefer it to work would be a good thing to do as well: https://code.google.com/p/google-plus-platform/issues/list
#interface ViewController() {
GTMOAuth2ViewControllerTouch *controller;
}
#end;
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
GPPSignIn *signIn = [GPPSignIn sharedInstance];
signIn.clientID = #""; // YOUR CLIENT ID HERE.
signIn.delegate = self;
}
- (IBAction)didTapSignIn:(id)sender {
void (^handler)(id, id, id) =
^(GTMOAuth2ViewControllerTouch *viewController,
GTMOAuth2Authentication *auth,
NSError *error) {
[self dismissViewControllerAnimated:YES completion:^{
[controller release];
}];
if (error) {
NSLog(#"%#", error);
return;
} else {
BOOL signedIn = [[GPPSignIn sharedInstance] trySilentAuthentication];
if(!signedIn) {
NSLog(#"Sign In failed");
}
}
};
controller = [[GTMOAuth2ViewControllerTouch
controllerWithScope:kGTLAuthScopePlusLogin
clientID:[GPPSignIn sharedInstance].clientID
clientSecret:nil
keychainItemName:[GPPSignIn sharedInstance].keychainName
completionHandler:handler] retain];
[self presentViewController:controller animated:YES completion:nil];
}
- (void)finishedWithAuth:(GTMOAuth2Authentication *)auth
error:(NSError *)error {
if (!error) {
UIAlertView * al = [[UIAlertView alloc] initWithTitle:#"Authorised"
message:#"Authorised!"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[al show];
[al release];
}
}
The only real trick to this code is that it uses the [GPPSignIn sharedInstance].keychainName - this means that the auth tokens get stored in the same keychain entry as the GPPSignIn button would, which in turn means we can use [[GPPSignIn sharedInstance] trySilentAuthentication] once it has been populated, and keep the same callback based flow as the main library.
#PeterLapisu approach works good if the Google Plus App is not installed.
Then outgoing url prefixes from app are as follows:
#"googlechrome-x-callback:"
#"https://accounts.google.com/o/oauth2/auth"
However if the Google App is installed there is one more outgoing url and the prefix list looks as follows:
#"com.google.gppconsent.2.4.1:"
#"googlechrome-x-callback:"
#"https://accounts.google.com/o/oauth2/auth"
So, if the Google App is installed, it will be launched simultaneously with our app UIViewController that contains webview.Then if user sucessfully logs in with Google App he will be directed back to our app and the ViewController will be visible.
To prevent this Google App should be allowed to login user and direct him back to our app. According to this discussion: https://code.google.com/p/google-plus-platform/issues/detail?id=900 it is allowed by Apple.
So in my implementation firstly I am checking if the Google App is installed:
- (BOOL)openURL:(NSURL*)url {
NSURL *googlePlusURL = [[NSURL alloc] initWithString:#"gplus://plus.google.com/"];
BOOL hasGPPlusAppInstalled = [[UIApplication sharedApplication] canOpenURL:googlePlusURL];
if(!hasGPPlusAppInstalled)
{
if ([[url absoluteString] hasPrefix:#"googlechrome-x-callback:"]) {
return NO;
} else if ([[url absoluteString] hasPrefix:#"https://accounts.google.com/o/oauth2/auth"]) {
[[NSNotificationCenter defaultCenter] postNotificationName:ApplicationOpenGoogleAuthNotification object:url];
return NO;
}
}
return [super openURL:url];
}
EDIT:
Now I can confirm that my app was finally approved with this solution.
I hope it will help somebody.
It merges Google+ and Gmail samples and completely avoids using Google SignIn Button, i.e you do not leave the app.
Add both Google+ and Gmail API to you Google project, in your app login to google as you would to gmail using GTMOAuth2ViewControllerTouch.xib from OAuth2 and set scope to Google+:
-(IBAction)dologin{
NSString *scope = kGTLAuthScopePlusLogin;//Google+ scope
GTMOAuth2Authentication * auth = [GTMOAuth2ViewControllerTouch
authForGoogleFromKeychainForName:kKeychainItemName
clientID:kClientID
clientSecret:kClientSecret];
if ([auth refreshToken] == nil) {
GTMOAuth2ViewControllerTouch *authController;
authController = [[GTMOAuth2ViewControllerTouch alloc]
initWithScope:scope
clientID:kClientID
clientSecret:kClientSecret
keychainItemName:kKeychainItemName
delegate:self
finishedSelector:#selector(viewController:finishedWithAuth:error:)];
[[self navigationController] pushViewController:authController animated:YES];
}else{
[auth beginTokenFetchWithDelegate:self didFinishSelector:#selector(auth:finishedRefreshWithFetcher:error:)];
}
}
and RETAIN the authentication object if signed in successfully, then use that auth object when using google plus services:
GTLServicePlus* plusService = [[[GTLServicePlus alloc] init] autorelease];
[plusService setAuthorizer:self.auth];//!!!here use our authentication object!!!
No need for GPPSignIn.
Full write up is here: Here is Another Solution
Use the (new) Google Sign In iOS SDK.
The SDK natively supports Sign In through WebView when no Google app is present to complete the Sign In process. It also supports potential dispatch to several Google apps for this purpose.

iPhone Gigya integration sharing Facebook, twitter inconsistency

I am currently integrating facebook and twitter for iphone with gigya.
For Twitter sometimes its sharing and sometimes don't. Facebook also happening same as well.
lately facebook not even opening its login in screen. On device unlike simulator delegate methods like LoginDidFail, DidLogin called more than once.not sure why.
I am not storing any object to store provider info when login happens.
Can you please let me know why this inconsis
This seems like a multi part question.I would need more information to get a clearer understanding. Please provide code snippets if possible. Meanwhile, please see see my responses below:
Inconsistent Sharing
This might be something to do with how your userAction is being defined.
(http://wiki.gigya.com/030_API_reference/010_Client_API/010_Objects/UserAction_object)
Login Screen not loading
Typically this is down to social Network Applications not set up correctly.
(http://wiki.gigya.com/035_Socialize_Setup/005_Opening_External_Applications)
Event Delegate methods called repeatedly
This may be down do multiple instances of the GSAPI class.
Hope that helps.
I am using following code snippet
GSAPI *gsAPI // declared this in header file
gsAPI = [[GSAPI alloc] initWithAPIKey:XX viewController:self]; // i kept this in viewDidload
// add this code to have facebook and twitter on provider list. this was put in in one method which will be called when user tries to share
GSDictionary *pParams5 = [[GSDictionary new] autorelease]; [pParams5 putStringValue:#"facebook,twitter" forKey:#"enabledProviders"];
[gsAPI showAddConnectionsUI:pParams5 delegate:self context:nil];
//this method called when login fails -(void)gsLoginUIDidFail:(int)errorCode errorMessage:(NSString*)errorMessage context:(id)context {
}
// this method called on successful login
- (void) gsLoginUIDidLogin:(NSString*)provider user:(GSDictionary*)user context:(id)context {
GSDictionary *userAction = [[GSDictionary new] autorelease];
[userAction putStringValue:#"title" forKey:#"title"];
[userAction putStringValue:#"userMessage" forKey:#"userMessage"];
[userAction putStringValue:#"desc" forKey:#"description"];
[userAction putStringValue:#"imageurl" forKey:#"linkBack"];
GSDictionary *pParams5 = [[GSDictionary new] autorelease];
[pParams5 putGSDictionaryValue:userAction forKey:#"userAction"];
[gsAPI sendRequest:#"socialize.publishUserAction" params:pParams5 delegate:self context:nil];
}
-(void) gsDidReceiveResponse:(NSString*)method response:(GSResponse*)response context:(id)context {
//showing alert messages on successful sharing
//this method getting called more than twice on device
}

FBConnect for iPhone: sessionDidNotLogin, sessionDidLogout, session didLogin not called the second time I load a view

My problem is very similar to this question, however I am posting a new one, as the answer to the aforementioned does not seem to solve my problem.
I have a multiview application - the first view is where the user logs in to Facebook, and the second where he picks an image and uploads it there. The first time the app runs, everything works fine, however if I return to the login view and press logout, then any calls to sessionDidNotLogin, sessionDidLogout or session didLogin don't seem to work.
I found out that the first time, if I NSLog(#"%#",session.delegates); I have 2; my LoginViewController and the FBLoginButton. However, apart from that first time, the same log prints only the LoginViewController and not the FBLoginButton. I guess this is connected somehow, but I don't know how to solve it.
Do I have to manually add the FBLoginButton to the session delegates, or I'm doing something else wrong here?
Thank you for any help/suggestion.
I figured out the problem, thanks to a post from the Facebook developers forum.
Everytime a view was loaded, I was creating a new session instance (even though I thought that it would be a singleton and the library should have taken care of it).
So, in order to make this work:
fbSession = [FBSession session];
if (!fbSession) {
fbSession = [[FBSession sessionForApplication:kApiKey secret:kApiSecret delegate:self] retain];
} else {
[[fbSession delegates] addObject:self];
}

Require user to go through login screen before giving access to iPhone app?

On iPhone, how do I show a login screen to get username and password before giving access to iPhone app? Also, does the iPhone store a cookie to the secure website like a web browser?
I was thinking of giving users to my website a long API key to store in the settings of their iPhone instead of asking them to login with a username/password (seems to be the Slicehost iPhone app approach.) Which is the best way to get a user to login securely? I have full control over the design of the iPhone app and website so have a lot of flexibility.
I have this need in a couple of apps, and I use the following pattern:
In the controller for the first view that gets presented to the user, I have the following test, where UserPrefsManager is a singleton that knows the user credentials. This call causes a modal view to appear (FirstTimeWelcomeViewController) which tells the person that they need to register.
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UserPrefsManager * prefs = [UserPrefsManager sharedInstance];
if (![prefs isLoggedIn])
{
FirstTimeWelcomeViewController * vc = [[[FirstTimeWelcomeViewController alloc] initWithNibName:#"FirstTimeWelcomeViewController" bundle:nil] autorelease];
[self presentModalViewController:vc animated: false];
}
else
{
if (![[RWXLocationSingleton sharedInstance] hasLocation]) {
[[RWXLocationSingleton sharedInstance] findLocationWithAccuracy:kCLLocationAccuracyThreeKilometers withObject:self andSelector:#selector(updateLocationsView)];
}
[[self tableView ]reloadData];
}
}
FirstTimeWelcomeViewController is basically a screen with buttons that greets people and takes them to the various ways to log in:
-(IBAction) createAccount
{
UIViewController * parent = [self parentViewController];
CreateAccountViewController * vc = [[[CreateAccountViewController alloc] initWithNibName:#"CreateAccountViewController" bundle:nil] autorelease];
[self dismissModalViewControllerAnimated:false];
[parent presentModalViewController:vc animated: false];
}
imagine that these also exist for forgotPassword, and loginACcount... same pattern. This causes the current view to be replaced by a view handling the specific case that they've pressed the button for.
taking the 'loginAccount' method, you've opened the LoginAccountViewController, and it has a method called loginButton, which works something like this...
-(IBAction) loginButton
{
NSString * u = [self.username text];
NSString * p = [self.password text];
//
// app specific logic that tests various inputs and creates a user object.
//
// goes here...
//
if([user checkValid])
{
UserPrefsManager * prefs = [UserPrefsManager sharedInstance];
[prefs setPassword:p];
[prefs setUsername:u];
[self dismissModalViewControllerAnimated:FALSE];
}
//
// more app specific stuff
//
}
And that's pretty much that. You have to use one of the standard ways for putting stuff in the keychain or user defaults to save your information. check that the one you pick lasts between restores if it is something that is annoying for the user to recreate. The first part is the most useful bit, thought the rest might be useful for context.
I'd recommend looking at a technology such as OAuth (http://oauth.net/). This enables the user to authorize access to their account to an application without having to actually enter their username and password into it. Also means you dont have to worry about secure storage of the users credentials, and if their passwords change etc it doesn't matter.
It's used by facebook / twitter and a lot of other big name companies so its not going away (Its actually in the process of getting approved as an offical standard).
You can't restrict access to the app itself but it is trivial to present an initial view that ask for the username and password. If the user does not present the correct information then the app will not allow them to progress to the next view which actually loads the web site.
Likewise, it would be trivial to store a key in the app's sandboxed folders such that neither the user nor anyone else could access it save through the app.

iPhone Facebook Connect logout crash after changing views

I've implemented Facebook Connect in my app just like the sample app that Facebook provides and it works well. After a user chooses to share data via Facebook they are taken to a new view and presented with the FB login dialog. When the user is done they exit the FB sharing view and return to my app's previous view. The user stays logged in as long as they don't logout - even if they exit the FB sharing view. This is good and as expected.
I'm using the same viewDidLoad method as the sample SessionViewController.m, and this is where _session is initialized:
- (void)viewDidLoad {
[_session resume];
_loginButton.style = FBLoginButtonStyleWide;
}
However I noticed that if the user presses the Logout button after exiting and re-loading the FB sharing view it will throw SIGABRT or EXC_BAD_ACCESS and crash the app. The EXC_BAD_ACCESS error occurs at the [dialog show] line of the Login button's touchUpInside method:
- (void)touchUpInside {
if (_session.isConnected) {
[_session logout];
} else {
FBLoginDialog* dialog = [[[FBLoginDialog alloc] initWithSession:_session] autorelease];
[dialog show];
}
Even though the user is connected the touchUpInside method is seeing a disconnected session... Sometimes instead of crashing after pressing the Logout button the logout will be successful but the view's status text remains "Logged in as ..." and the set status/upload image buttons are not hidden. Trying to log in again throws a SIGABRT, which looks due to a nil _session.sessionKey in FBRequest.m:344:
[_params setObject:_session.sessionKey forKey:#"session_key"];
Is there something I should be retaining or doing differently across view changes?
EDIT: I found another user having this same issue on the Facebook Developer Forums:
http://forum.developers.facebook.com/viewtopic.php?pid=193727#p193727
There is no solution posted but if I find one I'll update this question.
_session is nil at that point, right (that's consistent with the "always showing not connected"). Where do you initialize _session?
ETA: This is your code, which is taking the place of SessionViewController provided by Facebook Connect, right?
I can't figure out how _session could have been released, but that's what it seems like.
Adding:
You might want to change your button handler to
[_session resume];
and then do whatever you need to do in the didLogin delegate handler. That way the _session instantiates the FBLoginDialog for you.