Using applicationDidBecomeActive To Check If User Logged In - ios

I have an app that has a "Home Screen" with login and register options. When a user completes one of the above the data is stored in a shared instance. Now for security reasons I was looking at using the applicationDidBecomeActive to periodically check to make sure the user is still active on the server or not blocked by calling a method in the shared instance. If the user is not active the app kicks them to the home screen with a prompt.
My issue is however that when the app loads for the first time `applicationDidBecomeActive is called and because the user is not logged in you end up with a loop.
What is the correct approach for dealing with this issue? Ideally I want to use applicationDidBecomeActive but I only want to perform the check whilst in the account section of the app.
Any help would be great.
Thanks.

There are several ways of implementing this.
Way 1: Fire a Notification using NotificationCenter.default and implement a listener in your Accounts-ViewController.
Way 2: In your AppDelegate, get active ViewController (depends on which rootViewController you are using) and if that ViewController has the type AccountVC trigger a public function.
Way 3: Combine or use Way1 and Way2 in a different object and notify your ViewController in your preferred way.
And many more.
Post some code for more specific help :)

You can use either of the following methods to deal with the issue.
Approach 1
- (void)checkWhetherAppIsActive {
UIApplicationState appState = [[UIApplication sharedApplication] applicationState];
if (appState == UIApplicationStateActive) {
}else if (appState == UIApplicationStateInactive) {
}else if (appState == UIApplicationStateBackground) {
}
}
Approach 2
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
}];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
}];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
}];

When the user logs in , generate an expiryTimeStamp. And whenever the app is active, you can check the currentTimestamp to expiryTimeStamp . If the currentTimestamp is before the expiryTimeStamp, then consider it as active session!
- (void)applicationDidBecomeActive:(UIApplication *)application {
//compare current NSDate with expiryDate
if(current date is before expiry date){
//active session
}else{
//log out the user
}
}
You can generate expiry date as:
-(void)generateExpiryTimeStamp{
[[NSUserDefaults standardUserDefaults]setObject:[NSDate dateWithTimeIntervalSinceNow:900] forKey:#"tokenExpiry"]; //Expiry date Set to 15mins
[[NSUserDefaults standardUserDefaults]synchronize];
}

Related

I want to do a badge increment on the main app icon while receiving a notification in inactive mode of the app

I am working on a chat app in react-native iOS. I want to do a badge increment on the main app icon when a notification is received, when the app is killed or force quit by the user. It works well when the app is in the background mode. But the didReceiveRemoteNotification method is not called when the app is inactive. Any idea on this? I added code inside the didReceiveRemoteNotification method of AppDelegate.
Added the following code set in AppDelegate file
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
[RNNotifications didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
[RNNotifications didFailToRegisterForRemoteNotificationsWithError:error];
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSLog(#"APPDELEGATE: didReceiveRemoteNotification:fetchCompletionHandler %#", userInfo);
int badge = (int) application.applicationIconBadgeNumber;
if ( application.applicationState == UIApplicationStateInactive ) {
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:badge+1];
}
else if(application.applicationState == UIApplicationStateBackground) {
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:badge+1];
}
else if(application.applicationState == UIApplicationStateActive) {
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:badge+1];
}
completionHandler(UIBackgroundFetchResultNewData);
}
You need to create a UNNotificationServiceExtension to handle notifications when app is in background/killed.
It's a small application that will be executed on notification reception and has limited time to edit its content before presenting it to the user.
When you receive a notification in it's didReceive(_:withContentHandler:) delegate callback you can modify badge value of UNNotificationContent
In order to catch the incoming notification when the app is in the background or killed, use a UNNotificationServiceExtension in your project. The Info.plist for the UNNotificationServiceExtension (not the normal Info.plist for the main app; that one has normal things for the main app) might look something like
In the - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler of the UNNotificationServiceExtension, you can update the badge the following way:
self.bestAttemptContent = [request.content mutableCopy]; to get the request's content
self.bestAttemptContent.badge = <desired_integer_value> , where <desired_integer_value> would be the integer that you wish to put for the badge count.
self.contentHandler(self.bestAttemptContent); to complete the update of the content.
In many cases, the badge count may need to reflect a value (like number of unread chat messages) for a particular user. For that, you can use a shared user defaults. In fact NSUserDefaults supports the concept of app suite to allow such sharing. See Apple documentation for more details. In particular,
You can use this method when developing an app suite, to share preferences or other data among the apps, or when developing an app extension, to share preferences or other data between the extension and its containing app.
The argument and registration domains are shared between all instances of NSUserDefaults.
In a Constants.h file, have something to track individual counts for each user like
#define NOTIFICATIONS_UNREAD_SHARED [NSString stringWithFormat:#"notificationsUnread-%#",[mySharedDefaults objectForKey:USERNAME]]
and in your app, you would save the individual counts for each user, to the app suite's shared user defaults, with something like
NSUserDefaults *mySharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.com.yourCompany.yourAppname"];
if ([[NSUserDefaults standardUserDefaults] objectForKey:USERNAME]) {
mySharedDefaults setObject:[[NSUserDefaults standardUserDefaults] objectForKey:USERNAME] forKey:USERNAME];
[mySharedDefaults setObject:[NSNumber numberWithInteger:arrExistingRead.count] forKey:NOTIFICATIONS_READ_SHARED];
[mySharedDefaults setObject:[NSNumber numberWithInteger:([AppConstant sharedconstant].countObj.arrAllMessages.count - arrExistingRead.count)] forKey:NOTIFICATIONS_UNREAD_SHARED];
[mySharedDefaults synchronize];
}
Then in your UNNotificationServiceExtension, you would do something like
NSUserDefaults *mySharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.com.yourCompany.yourAppname"];
if ([mySharedDefaults objectForKey:NOTIFICATIONS_UNREAD_SHARED]) {
if ([mySharedDefaults objectForKey:USERNAME]) {
self.bestAttemptContent.badge = [NSNumber numberWithInt:[[mySharedDefaults objectForKey:NOTIFICATIONS_UNREAD_SHARED] intValue]+1];
} else { // username somehow not set; reset badge to 0
self.bestAttemptContent.badge = #0;
}
} else { // notifications unread count not found for this user; reset badge to 0
self.bestAttemptContent.badge = #0;
}
Troubleshooting
In case the extension doesn't appear to be receiving the push notifications, some things to verify:
Look at the build targets. Besides the main app, there should be one for the extension too.
In the settings for the main app, it should associate with the UNNotificationServiceExtension :
You need to set the badge field in the payload in the push notification.
https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification
You will have to do the calculation server side.

Is it necessary to synchronize access to a simple value when used from multiple queues?

Consider the following example:
#implementation AppDelegate
{
UIDeviceOrientation _orientation;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UIDevice *device = [UIDevice currentDevice];
_orientation = device.orientation;
[[NSNotificationCenter defaultCenter] addObserverForName:UIDeviceOrientationDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
// setting _orientation happens on the main thread
_orientation = device.orientation;
}];
return YES;
}
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
// accessing _orientation on a background queue
[self _doStuffWithBuffer:sampleBuffer orientation:_orientation];
}
#end
I have camera buffers coming in on a background queue. In order to process them I need access to the current device orientation. However, getting that requires access from the main queue.
In examples like this, is it fine if the main queue is occasionally writing to simple values while the background queue is reading them? I could wrap all reads and writes in an os_unfair_lock but I am hoping to avoid the perf hit of taking a lock in the read case which happens a lot.
If this example is ok, in what cases is it not ok? I assume reading/writing objects is a no-go; what about structs?

ios Magical Record save on UIApplicationWillTerminateNotification

I have app that stores system events to Core Data Database. To perform saving I use MagicalRecord.
So, in my logger class in init I have:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleDidFinishLaunchingNotification) name:UIApplicationDidFinishLaunchingNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleWillTerminateNotification) name:UIApplicationWillTerminateNotification object:nil];
In handle functions I store simple entity with text and timestamp property:
- (void)handleDidFinishLaunchingNotification
{
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
DBMessage *dbMessage = [DBMessage createEntityInContext:localContext];
dbMessage.text = #"Did finish launching";
dbMessage.timestamp = [NSDate date];
}];
}
- (void)handleWillTerminateNotification
{
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
DBMessage *dbMessage = [DBMessage createEntityInContext:localContext];
dbMessage.text = #"Will terminate";
dbMessage.timestamp = [NSDate date];
}];
}
When I open and close (without crash) app few times, in my DB I can see "Did finish launching" entries (more that one, so I'm sure app was closed, not only moved to BG), but none of "Will terminate".
I would be less surprised if the launching event were missed, because I could expect that init method will be called after notification is posted.
What I can do to store terminate events?
I have an answer:
It looks like when application gets terminated, wont't let me to save in new background thread. But saving in current thread works fine. So all I had to do was change saving method to save in current thread, by calling saveWithBlockAndWait: instead of saveWithBlock:
- (void)handleWillTerminateNotification
{
[MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext *localContext) {
DBMessage *dbMessage = [DBMessage createEntityInContext:localContext];
dbMessage.text = #"Will terminate";
dbMessage.timestamp = [NSDate date];
}];
}
Now events are succesfully saved on DB.

How to detect if user is online when using Firebase iOS SDK

Before sending a message (i.e. calling setValue on a Firebase object), is there a recommended way to determine if the user is online or offline?
For example:
[firebase setValue:someValue withCompletionBlock:^(NSError *error, Firebase *ref) {
// This block is ONLY executed if the write actually completes. But what if the user was offline when this was attempted?
// It would be nicer if the block is *always* executed, and error tells us if the write failed due to network issues.
}];
We need this in our iOS app because the user could lose connectivity if they went into a tunnel for instance. If Firebase doesn’t offer a built-in way to do this, we’ll just resort to monitoring iOS's Reachability API.
They have a section of their docs devoted to this here.
Basically observe the .info/connected ref
Firebase* connectedRef = [[Firebase alloc] initWithUrl:#"https://SampleChat.firebaseIO-demo.com/.info/connected"];
[connectedRef observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot, NSString *prevName) {
if([snapshot.value boolValue]) {
// connection established (or I've reconnected after a loss of connection)
}
else {
// disconnected
}
}];
You can do something like this. Setup the observer and post notification on status change. Basically the same as accepted answer but adapted to new version of firebase framework.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
FIRDatabaseReference *ref = [[FIRDatabase database] referenceWithPath:#".info/connected"];
[ref observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
NSString *value = snapshot.value;
NSLog(#"Firebase connectivity status: %#", value);
self.firebaseConnected = value.boolValue;
[[NSNotificationCenter defaultCenter] postNotificationName:#".fireBaseConnectionStatus" object:nil];
}];
}
Then in any view controller of your app you can do this. Observe notifications and do something based on that (update your ui, etc).
- (void) fireBaseConnectionStatus:(NSNotification *)note
{
AppDelegate *app = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[self updateButtons:app.firebaseConnected];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(fireBaseConnectionStatus:) name:#".fireBaseConnectionStatus" object:nil];
}
Hope this will help.
PS. Perhaps you will find it interesting idea to also monitor basic reachability with well known reachability.[mh] framework. Then you also could decide how do you act in case that firebase is connected on wifi or 3g.
Swift 3
let connectedRef = FIRDatabase.database().reference(withPath: ".info/connected")
connectedRef.observe(.value, with: { snapshot in
if let connected = snapshot.value as? Bool, connected {
print("Connected")
} else {
print("Not connected")
}
})
More info - https://firebase.google.com/docs/database/ios/offline-capabilities

Box.com SDK for iOS: Checking authorization status

The box.com SDK for iOS has an object called sharedSDK that holds another object called OAuth2Session. OAuth2Session has a property called isAuthorized. On each application launch this property is set to NO. Even if I keep the refreshToken inside the system Keychain, and assign it at launch like so:
//...applicationDidFinisLaunching...
NSString *token = [controllerObject fetchFromKeychainForKey:#"com.box.token"];
[BoxSDK sharedSDK].OAuth2Session.refreshToken = token;
if ([BoxSDK sharedSDK].OAuth2Session.isAuthorized) {
//Not until signing in
NSLog(#"Authorized.)";
} else {
NSLog(#"Not Authorized.");
}
What should I be doing differently to check auth status? The Dropbox SDK has a method to determine if the session is linked, persists through launches.
I'm the author of the iOS SDK. The isAuthorized method is only a best guess of whether or not the current OAuth2 tokens are valid. From the documentation:
Compares accessTokenExpiration to the current time to determine if an access token may be valid. This is not a guarantee that an access token is valid as it may have been revoked or already refreshed.
Because accessTokenExpiration is not stored anywhere by the Box iOS SDK, this field will be nil following initialization, even if the refresh token is loaded.
The Box iOS SDK takes the stance that the Box API is the source of truth about state and does not attempt to perform client side checks that can be handled more reliably by the server.
The recommended way of reloading the OAuth2 session is to set the refresh token from the keychain as you have done and then issue a "heartbeat" API call to trigger an autorefresh or fail if the refresh token is invalid.
An example of this can be found in the Box iOS SDK sample app
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(boxAPIAuthenticationDidSucceed:)
name:BoxOAuth2SessionDidBecomeAuthenticatedNotification
object:[BoxSDK sharedSDK].OAuth2Session];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(boxAPIAuthenticationDidFail:)
name:BoxOAuth2SessionDidReceiveAuthenticationErrorNotification
object:[BoxSDK sharedSDK].OAuth2Session];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(boxAPIInitiateLogin:)
name:BoxOAuth2SessionDidReceiveRefreshErrorNotification
object:[BoxSDK sharedSDK].OAuth2Session];
// attempt to heartbeat. This will succeed if we successfully refresh
// on failure, the BoxOAuth2SessionDidReceiveRefreshErrorNotification notification will be triggered
[self boxAPIHeartbeat];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)boxAPIHeartbeat
{
[[BoxSDK sharedSDK].foldersManager folderInfoWithID:BoxAPIFolderIDRoot requestBuilder:nil success:nil failure:nil];
}
#pragma mark - Handle OAuth2 session notifications
- (void)boxAPIAuthenticationDidSucceed:(NSNotification *)notification
{
NSLog(#"Received OAuth2 successfully authenticated notification");
BoxOAuth2Session *session = (BoxOAuth2Session *) [notification object];
NSLog(#"Access token (%#) expires at %#", session.accessToken, session.accessTokenExpiration);
NSLog(#"Refresh token (%#)", session.refreshToken);
[self dismissViewControllerAnimated:YES completion:nil];
BOXAssert(self.viewControllers.count == 1, #"There should only be one folder in the hierarchy when authentication succeeds");
BoxFolderViewController *rootVC = (BoxFolderViewController *)self.topViewController;
[rootVC fetchFolderItemsWithFolderID:BoxAPIFolderIDRoot name:#"All Files"];
}
- (void)boxAPIAuthenticationDidFail:(NSNotification *)notification
{
NSLog(#"Received OAuth2 failed authenticated notification");
NSString *oauth2Error = [[notification userInfo] valueForKey:BoxOAuth2AuthenticationErrorKey];
NSLog(#"Authentication error (%#)", oauth2Error);
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)boxAPIInitiateLogin:(NSNotification *)notification
{
NSLog(#"Refresh failed. User is logged out. Initiate login flow");
dispatch_sync(dispatch_get_main_queue(), ^{
[self popToRootViewControllerAnimated:YES];
NSURL *authorizationURL = [BoxSDK sharedSDK].OAuth2Session.authorizeURL;
NSString *redirectURI = [BoxSDK sharedSDK].OAuth2Session.redirectURIString;
BoxAuthorizationViewController *authorizationViewController = [[BoxAuthorizationViewController alloc] initWithAuthorizationURL:authorizationURL redirectURI:redirectURI];
BoxAuthorizationNavigationController *loginNavigation = [[BoxAuthorizationNavigationController alloc] initWithRootViewController:authorizationViewController];
authorizationViewController.delegate = loginNavigation;
loginNavigation.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:loginNavigation animated:YES completion:nil];
});
}
This view controller registers for OAuth2 notifications which are triggered in the event of a successful refresh or a logout. In the selectors you register for these callbacks, you can load a view controller in your app or load the BoxAuthorizationViewController to log a user in.

Resources