I have an app in which the user may choose to login to FB. My code is based largely on the tutorials at FB, and for the most part, the app, and the FB integration works as expected. The problem I am having is that the app is not remembering from launch to launch that the user has selected to connect the app to FB. I put a check into AppDelegate.m to check for a cached FBSession:
if (FBSession.activeSession.state == FBSessionStateCreatedTokenLoaded) {
// Yes, so just open the session (this won't display any UX).
NSLog(#"The state is IS 'State created token loaded'");
[self openSessionWithAllowLoginUI:NO];
} else {
// No, display the login page.
NSLog(#"The state is NOT 'State created token loaded'");
[self openSessionWithAllowLoginUI:YES];
}
Every time I launch the app, the line "The state is NOT 'State created token loaded'" is displayed in the console. This makes me think I am not doing something right in order to make that FB login persist from launch to launch.
I could really use some advice here. What does one need to do to ensure that "FBSession.activeSession.state == FBSessionStateCreatedTokenLoaded" is true on app launch?
If you have this code right when the app starts, then note that:
FBSession.activeSession
May not be set yet. What you want to do to check for a cached token is something like:
if (![self openSessionWithAllowLoginUI:NO]) {
[self openSessionWithAllowLoginUI:YES];
}
The first call with the "NO" will return synchronously with a value of true if there was a a cached token. It returns no if there is no cached token. At this point you can force the login UX to happen.
Related
Scenario:
I want to call the logout function if the app is terminated. I'm able to do it using native code:
- (void)applicationWillTerminate:(UIApplication *)app
{
// Run Logout function
}
Problem:
How to do it in IBM mobilefirst hybrid app?
// ************************************************
Edited
First of all, user login in to the app, if the user key in the correct user id and password, it will add the userIdentity into "loginRealm".
WL.Server.setActiveUser("loginRealm", userIdentity);
Next, user closes the apps without logout. So, when the user login for the another time, MFP server will not return any feedback since it will hit this exception:
Cannot change identity of an already logged in user in realm
'loginRealm'. The application must logout first.
Hence, I have to logout the user from MFP server by setting the "loginRealm" to null in adapter;
WL.Server.setActiveUser("loginRealm", null);
The above line of code is in the logout function defined in authentication-config.xml.
The client side device runs this line of code and it will trigger the logout function. Besides, it will reload the App upon success:
WL.Client.logout('loginRealm', {
onSuccess: WL.Client.reloadApp
});
Steps that I've tried:
1) At WlcommonInit() I added WL.Client.updateUserInfo(); and if WL.Client.isUserAuthenticated("loginRealm") return true I will logout the user from server. However, WL.Client.isUserAuthenticated("loginRealm") will always return false. This is because, it needs to take sometime around (30seconds to 2 minutes) for the flag to turn true after WL.Client.updateUserInfo();. So my login still fail and hit the same error.
2) I tried to logout the users during the user click login button. But the app will refresh and return to login page again due to reloadApp. The logout code I get from IBM mobilefirst website. So user need to click and type 2 times in order to login into the main menu.
WL.Client.logout('loginRealm', {
onSuccess: WL.Client.reloadApp
});
Am I doing it wrongly? Or are there any other methods to get WL.Client.isUserAuthenticated("loginRealm") return true instantly after WL.Client.updateUserInfo(); ? Can we remove the reload app line of code in logout function?
I don't think this is doable, because that logout function (in MFP) will require server connectivity (request and response) and if the app is by then killed, I think it's going to cause unpredictable results.
Note though that it seems to be not recommended to use that function anyway? applicationWillTerminate when is it called and when not
What you should do perhaps in order to simulate it, is to logout-on-login, so that it would appear that the app is logged out when opening it. You can extend the duration of the splash screen so that the end-user will not see that s/he is logged in (in case the session was still alive between the closing and re-opening of the app), until really logged out and then you can display the login screen again or any other required screen.
Currently I have a Twitter login that authenticates the user just fine, receiving all the appropriate information for the user necessary for confirming a complete login.
I then am calling the [PFTwitterUtils logInWithBlock:...] method in order to authenticate the user through Parse and populate a new user in _User.
(I am not using just this method to present the Twitter login dialog box as I could not get it to present itself).
Here is the strange part:
On the first time I launch the app, I sign in with Twitter, and then call the logInWithBlock method and receive the following error:
Something went wrong: The operation couldn’t be completed. (NSURLErrorDomain error -1012.)
The strange part is that this issue does not occur at all if I relaunch the app again. The only difference is that upon relaunch I believe the Twitter Account information that I had used before (via browser not accounts) is saved on the device. When I launch the app again, I have it set so that it reloads the previous session: this time the [PFTwitterUtils logInWithBlock:...] works like a charm, creates a new user, etc., without any issue.
NOTE: I have a valid URL saved as the Callback URL on my Twitter App's settings. Also, I hope it is clear that the setup was done correctly given that it works fine on second log in.
Here is the login code I am using:
// Login with Twitter through Parse
[PFTwitterUtils logInWithBlock:^(PFUser *user, NSError *error) {
if (error != nil) {
// Something went wrong
NSLog(#"Something went wrong: %#", [error localizedDescription]);
breakLogin = YES;
[self invalidateSignInTimer];
return;
}
else if (user.isNew){
userObjectID = user.objectId;
[user saveInBackground];
NSLog(#"New User");
}
else if (!user.isNew) {
userObjectID = user.objectId;
[user saveInBackground];
NSLog(#"Returning User. Welcome Back!");
}
}];
I am running out of patience and ideas on why this is happening. If anyone has any ideas please let me know - thanks!
UPDATE:
I noticed that the reason this seems to be happening is such:
The first time i attempt to login there is no PFUser, whereas the second time there seems to be a saved [PFUser currentUser], which allows the [PFTwitterUtils...] method to log in and create the new user.
Yet I am still not sure how to prevent the failure the first time without manually creating a new PFUser and then logging in with Twitter, then PFTwitterUtils...
UPDATE 2
I have resolved this issue with the answer I have provided below. I am leaving this up as I feel this is something that may help others out there in the future.
So after extraneous research, I seemed to have found a solution.
First I made sure that all of my framework files (SKDs, headers, etc) were up to date by removing all of their references from both the project and the library linking.
Second I made sure that all keys and secrets were remade and updated in my
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
Lastly I made double checked that I properly went through the set up for Twitter authentication through Parse's website.
BUT what I found to be the only solution rested in the Callback URL.
When specifying the Callback URL in your Twitter Application under "Settings", I had to uncheck the Lock Callback URL option, despite the recommendation by Twitter. (seen below)
I was a little unhappy that all of this trouble derived from something so mundane, however I was at least pleased that solution was in fact resolved without too much trouble. Hopefully this answer helps someone out there struggling with a similar issue.
In my iOS app I made a Login page as the entry point of the app (using storyboard)
However I don't want the user to see the login page each time he uses the app, so I thought of launching the Home page of the app if the user already performed a login in the past.
For that I started to save the login act with NSUserDefaults but I do not know in which of the LoginViewController method I should check for it? Also, is this way to feature "auto logging" a good practice?
Best place is in application:didFinishLaunchingWithOptions
if ([[NSUserDefaults standardUserDefaults] integerForKey:#"UserID"]==0) {
//no user login go back to login page
}
else{
//go in main screen of you application as user already login "root" is storyboard ID of main screen
self.window.rootViewController=[self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:#"root"];
}
My app starts and checks for the receipt. Because it is sandbox, the first time the app runs from Xcode, it needs to ask the App Store for the receipt. So I use SKReceiptRefreshRequest to request it.
A window pops up, asking for the App Store credentials. If I type the credentials, then the app loads the receipt, I validate it, and the app runs fine.
The problem starts if I cancel that credential window.
Then I have the first problem. At this time the app has no receipt, so I cannot validate to see if the copy is pirate. What to do? I tried the following approach: instead of disabling the application, when the user tries to use the app, I show a window saying "could not validate the application, type OK to validate now".
When the user types OK, I trigger SKReceiptRefreshRequest a second time. Again a credential window pops up, I type the valid credentials and nothing happens. After 2 or 3 minutes of nothingness, a windows pops up saying "cannot connect to App Store".
The strange part is that none request:didFailWithError: or requestDidFinish: methods of SKReceiptRefreshRequest delegate are called during this failure. Receipt retrieval fails without triggering any delegate method and yes, the delegate is assigned.
The code for the receipt retrieval is the traditional one, that is
SKReceiptRefreshRequest *refreshReceiptRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:nil];
refreshReceiptRequest.delegate = self;
[refreshReceiptRequest start];
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
NSLog(#"ERROR");
}
- (void)requestDidFinish:(SKRequest *)request {
if([request isKindOfClass:[SKReceiptRefreshRequest class]])
{
NSLog(#"App Receipt exists after refresh");
} else {
NSLog(#"Receipt request done but there is no receipt");
}
}
Apparently this is a bug of SKReceiptRefreshRequest. If the user cancels the first credential box, the application will not able to retrieve the receipt a second time, at least not in sandbox mode. Because this will not work on sandbox mode, you cannot test and this will also not work when Apple review your app and your app will be rejected.
Also, killing the app from the task bar will not help to make the credential box appear a second time.
The only solution is to present an alert, telling the user to remove and download your app again from the store and to not cancel the credential box when the app asks for the apple ID/password.
Is it possible to differentiate between the cases where
an iOS user has explicitly denied user notification permissions, and
an iOS user has never been prompted for permission?
My situation: In the past, I've prompted for user notification permission, but never kept track of requests myself. Later, I stopped attempting to register any notification settings. Now, I'd like to re-introduce user notifications.
After a significant event in the App, my plan is to display some sort of UI that explains the benefit of opting in to user notifications. However, if the user has already declined, I'd prefer to show a separate UI that can take them into Settings.app.
Currently, I'm using -[UIApplication currentUserNotificationSettings] to grab the current settings, but it appears that this returns UIUserNotificationTypeNone for both of the above described cases.
Personally I haven't found a way to determine this via a quick query of the iOS SDK.
However I have been able to track this myself recording when -[UIApplication application:didRegisterUserNotificationSettings:] is called.
When iOS calls this method, you can be sure the user has been prompted for user notification permissions and has (importantly) either accepted or denied it.
Storing when this occurs you can later check this value to determine if the prompt has been shown before or not.
Example Code:
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"ABHasPromptedForUserNotification"];
//... your other notification registration handling...
}
- (BOOL)hasPromptedForUserNotification {
return [[NSUserDefaults standardUserDefaults] boolForKey:#"ABHasPromptedForUserNotification"];
}
FYI: I've found it preferable to set "ABHasPromptedForUserNotification" as true in the in -[UIApplication application:didRegisterUserNotificationSettings:] rather than when I call -[UIApplication registerForRemoteNotifications] as in some situations the user can be shown the prompt multiple times. This can happen if the user backgrounds the app, or takes a call. In these cases the prompt will be hidden by iOS, and shown again if next time you call -[UIApplication registerForRemoteNotifications]. Setting this setting in the delegate avoids thinking the user has been prompted before and won't be prompted again in these edge cases.
No.
And I believe this is done intentionally. Because usual scenario is to register for remote notifications on every app launch. That means that user should not see permissions dialog each time he opens the app. iOS do this automatically. But if you will show extra screen before requesting permissions Apple can't allow you to know if user denied permissions in the past so you can show screen describing how user can enable his permissions through settings each time you want. This will undo all Apple did to stop irritating users.
In your case you must follow same strategy. Show only one type of explanatory screen on both scenarios and save user choice in NSUserDefaults to know if you must not show it again. Users who denied permissions previosly will not see permissions dialog. Though you'll have one benefit for new users (which is obviously you are trying to achieve): you may keep showing explanatory screen many times if user canceled it.
If you support iOS 10 and above the UNUserNotifications framework allows more granularity.
let current = UNUserNotificationCenter.current()
current.getNotificationSettings(completionHandler: { (settings) in
if settings.authorizationStatus == .notDetermined {
// Not requested
}
if settings.authorizationStatus == .denied {
// User said Don't allow
}
})
In case someone need to check the old UIUserNotification API for access permission. code below is tried and tested.
- (BOOL)isUserNotificationAllowed {
UIUserNotificationType types = [[UIApplication sharedApplication] currentUserNotificationSettings].types;
if(types & UIUserNotificationTypeBadge || types & UIUserNotificationTypeSound || types & UIUserNotificationTypeAlert){
return YES;
}
else {
return NO;
}
}
Update for iOS 10.0+.
Apple provide this for checking user's push notification permission status:
[[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
switch (settings.authorizationStatus) {
case UNAuthorizationStatusNotDetermined:
DDLogDebug(#"Not Determined");
break;
case UNAuthorizationStatusDenied:
DDLogDebug(#"Denied");
break;
case UNAuthorizationStatusAuthorized:
DDLogDebug(#"Authorized");
break;
case UNAuthorizationStatusProvisional:
DDLogDebug(#"Provisional");
break;
}
}];
Check Apple Doc for more information.