I'm trying to temporarily resume (un-suspend?) my replication tasks in couchbase lite iOS when my app receives a push notification. I've tried the following in my AppDelegate.m
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSLog(#"notification-body => %#", notification);
NSMutableArray *toSuspend = [NSMutableArray array];
CBLManager *manager = [CBLManager sharedInstance];
for (NSString* name in [manager allDatabaseNames]) {
CBLDatabase* db = [manager databaseNamed:name error:nil];
NSArray* replications = [db allReplications];
for(CBLReplication* rep in replications) {
if(rep.suspended) {
[rep setSuspended: NO];
[toSuspend addObject: rep];
}
}
}
[RCTPushNotificationManager didReceiveRemoteNotification:notification];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(20 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(#"notification-complete => %#", notification);
for(CBLReplication* rep in toSuspend) {
[rep setSuspended: YES];
}
completionHandler(UIBackgroundFetchResultNewData);
});
}
But the list of replicators is always empty even though I have 2 running. What am I doing wrong?
Related
The following code generally works but throws an SSL handshake failed (-9806) when the device is locked.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler
{
NSString *roomID = [userInfo objectForKey:#"roomId"];
if (roomID) {
//tell firebase to update this conversation
[self.fb updateChatRoomMessages:roomID withBlock:^(BOOL success) {
/*code gets to here as I can see that with breakpoints,
but before we get here we can see the SSL handshake
error in console (only when phone is locked)*/
handler(UIBackgroundFetchResultNewData);
}];
} else {
handler(UIBackgroundFetchResultNoData);
}
}
Now basically updateChatRoomMessages tries to talk to firebase but I assume the problem is with just about any network connection. Is there known any restriction of the sort?
Any ideas?
Update - rest of the code
(void)updateChatRoomMessages:(NSString *)roomID withBlock:(void (^)(BOOL))completionBlock{
ChatRoomSummary *room = [[DataCollections shared] getChatRoomById:roomID];
Firebase *ref = [[Firebase alloc] initWithUrl:[NSString stringWithFormat:#"%#/chatdata/messages/%#",
self.baseURL, roomID]];
[ref observeSingleEventOfType:FEventTypeValue withBlock:^(FDataSnapshot *allMsgs) {
dispatch_async(dispatch_get_main_queue(), ^{
[room.messages removeAllObjects]; //clearing the list of messages so that we update it
NSDictionary *dict = allMsgs.value;
for(NSString *snapshot in [dict allKeys]) {
NSDictionary *currentSnapshot = [dict objectForKey:snapshot];
[currentSnapshot setValue:snapshot forKey:#"messageID"];
[[DataEventListener shared] onNewMessage:currentSnapshot forRoom:room preventNotification:YES];
}
[Utility notify:NOTIFY_NEW_ROOM];
[self updateBadges:nil];
if (completionBlock) {
completionBlock(YES);
}
});
}];
}
If my app is running on foreground or background this working fine. I am receiving the notifications and save it on the local database. But if the app killed from the background it receives the remote notifications but the following method is not called. And the issue is if I tap any one of the notification,only that notification will saved on the local database.
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler
{
[PFPush handlePush:userInfo];
NSLog(#"Received notification: %#", userInfo);
NSString *alertString = [[userInfo objectForKey:#"aps"]valueForKey:#"alert"];
NSLog(#"%#",alertString);
NSString *msgType = [userInfo objectForKey:#"messageType"];
NSString *senderId = [userInfo objectForKey:#"senderId"];
NSString *receverId = [userInfo objectForKey:#"receverId"];
NSString *msg = [userInfo objectForKey:#"message"];
NSString *timeStr = [userInfo objectForKey:#"Time"];
NSLog(#"msg type%# senderId %# receverId %# message %#",msgType,senderId,receverId,msg);
if ([AppDelegate isNetworkReachable]){
if ([msgType isEqualToString:#"CHAT"]) {
Chatmessage *Cmsg=[[Chatmessage alloc]init];
Cmsg.chat_date =timeStr;
Cmsg.chat_image =#"";
Cmsg.chat_message = msg;
Cmsg.chat_Receiver_Id = receverId;
Cmsg.chat_Sender_Id = senderId;
NSLog(#"recid%#",Cmsg.chat_Receiver_Id);
NSMutableArray *arryMsg = [[NSMutableArray alloc]init];
arryMsg = [[DBModelNew database]getChatMessageBasedOnTime:receverId SenId:senderId time_stamp:timeStr message:msg];
if (arryMsg.count == 0) {
[[DBModelNew database]insertmsg:Cmsg];
}
[[NSNotificationCenter defaultCenter]postNotificationName:#"receivedmessage" object:nil];
chatHistory *chatObj = [[chatHistory alloc]init];
chatObj.chat_meta_id = [NSString stringWithFormat:#"%#",senderId];
chatObj.last_send_message = msg;
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = #"yyyy-MM-dd HH:mm:ss";
NSString *str=[dateFormatter stringFromDate:[NSDate date]];
chatObj.last_time_stamp = [self dateformat:str];
PFQuery *query = [PFUser query];
[query whereKey:#"objectId" equalTo:senderId];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (NSDictionary *dict in objects) {
[[dict objectForKey:#"ProfilePic"] getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error) {
if (data) {
UIImage *image = [UIImage imageWithData:data];
if (image) {
chatObj.fndimage = image;
chatObj.name = [dict objectForKey:#"name"];
[[DBModelNew database]insertChat:chatObj];
[[NSNotificationCenter defaultCenter]postNotificationName:#"receivedNewMessage" object:nil];
}
}
}
}];
}
}
}];
}
}
}
From the Apple docs, if the user hard closes the app it does not call the method.
In addition, if you enabled the remote notifications background mode,
the system launches your app (or wakes it from the suspended state)
and puts it in the background state when a remote notification
arrives. However, the system does not automatically launch your app if
the user has force-quit it. In that situation, the user must relaunch
your app or restart the device before the system attempts to launch
your app automatically again.
If you want to launch specific payload dictionary from viewDidLoad then you simply call the following :
UILocalNotification *localNotif =
[launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
And you get the userInfo this way:
NSString *msgType = [localNotif objectForKey:#"messageType"];
And now you can act accordingly. This is just for the circumstances you stated in your title. When the app is not 'running' (terminated)
as apple suggested use Handoff in Glance .
I wants to call web API in Glance Interface , for this I did following things
- (void)awakeWithContext:(id)context
{
[super awakeWithContext:context];
[self CreateUaerActivity];
}
-(void)CreateUaerActivity
{
NSUserActivity *activity = [[NSUserActivity alloc] initWithActivityType:#"com.xxx.xxx.glance"];
activity.title = #"Glance";
activity.delegate=self;
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:kUserLoginWatchKit,kRequestTypeWatchKit, nil];
activity.userInfo = dict;
self.userActivity = activity;
[self.userActivity becomeCurrent];
}
- (void)willActivate
{
[super willActivate];
[NSTimer scheduledTimerWithTimeInterval:120 target:self selector:#selector(doSomething) userInfo:nil repeats:YES];
}
-(void)doSomething
{
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:kUserLoginWatchKit,kRequestTypeWatchKit, nil];
[super updateUserActivity:#"com.xxx.xxx.glance" userInfo:dict webpageURL:nil];
}
-(void)handleUserActivity:(NSDictionary *)userInfo
{
//displaying data
}
and in AppDelegate.m file -
-(BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *))restorationHandler
{
NSLog(#"Handoff dictionary: %#", userActivity.userInfo);
NSString *requestType = userActivity.userInfo[kRequestTypeWatchKit];
if ([requestType isEqual: kGlanceDataWatchKit])
{
//calling web API to get Data
}
return YES;
}
I found AppDelegate never called continueUserActivity method to return something to Glance interface.
please guide me how to call API through Glance Interface.
I'm not sure if this is what you want, but if you want to call an web Api i suggest yout to do it like this :
in the GlanceInterfaceController :
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
[dictionary setObject:#"getSomething" forKey:#"action"];
[MainInterfaceController openParentApplication:dictionary reply:^(NSDictionary *replyInfo, NSError *error) {
NSLog(#"Reply received by Watch app: %#", replyInfo); // the reply from the appDelegate...
}
in your parent's app Delegate :
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
NSLog(#"Request received by iOS app");
if( [userInfo objectForKey:#"action"] isEqualToString:#"getSomething"] ){
//call you're Web API
//send the reponse to you're glance :
reply(DictResponse);// some Dictionary from your web API...
}
*****EDIT*****
i've been issued the same issue, one easy fix is to begin an background task, from :
fiveminutewatchkit
Here's the way :
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
// Temporary fix, I hope.
// --------------------
__block UIBackgroundTaskIdentifier bogusWorkaroundTask;
bogusWorkaroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bogusWorkaroundTask];
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] endBackgroundTask:bogusWorkaroundTask];
});
// --------------------
__block UIBackgroundTaskIdentifier realBackgroundTask;
realBackgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
reply(nil);
[[UIApplication sharedApplication] endBackgroundTask:realBackgroundTask];
}];
// Kick off a network request, heavy processing work, etc.
// Return any data you need to, obviously.
reply(nil);
[[UIApplication sharedApplication] endBackgroundTask:realBackgroundTask];
}
in fact iOS kill your parent's app before you can retrieve data... this (not very clean solution) prevent you're app to be killed... and let you the time to retrieve infos...
******END EDIT******
I am not sure where exactly my question fit. Here is the issue :
I want the push notifications to be registered on application start. For which I am registering in AppDelegate didFinishLaunchingWithOptions.
sem = dispatch_semaphore_create(0);
[manager registerForPushNotifications];
dispatch_semaphore_signal(sem);
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
If I don't use GCD, return YES; of didFinishLaunchingWithOptions is called first and in that case, my my service method, which want to call from didRegisterForRemoteNotificationsWithDeviceToken for sending device token is not called.
// system push notification registration success callback, delegate to pushManager
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSString *token = [[deviceToken description] stringByTrimmingCharactersInSet: [NSCharacterSet characterSetWithCharactersInString:#"<>"]];
token = [token stringByReplacingOccurrencesOfString:#" " withString:#""];
NSLog(#"content---%#", token);
[[NSUserDefaults standardUserDefaults]setObject:token forKey:#"deviceToken"];
[self registerForPushWooshNotification];
[[PushNotificationManager pushManager] handlePushRegistration:deviceToken];
}
-(void)registerForPushWooshNotification
{
NSDictionary *params = #{#"TokenId": [[NSUserDefaults standardUserDefaults]objectForKey:#"deviceToken"]
};
[_sharedHandler.requestManager POST:TGURL_PUSHWOOSH_NOTIFICATION
parameters:params
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSError *e;
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:[operation.responseString dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:&e];
NSLog(#"------ Registered for Pushwoosh ------");
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
}
But as I have implemented the GCD, didRegisterForRemoteNotificationsWithDeviceToken never gets called.
Summary :
1. I have to register application on app start.
2. Web Service needs to be called on app start.
3. If GCD not used : return YES; is called first, and didRegisterForRemoteNotificationsWithDeviceToken gets called after delay.
4. If GCD used : didRegisterForRemoteNotificationsWithDeviceToken is never called.
I had searched for didFinishLaunchingWithOptions wait and didRegisterForRemoteNotificationsWithDeviceToken called after delay on google before posting this question but no success.
The problem here is that by waiting on the semaphore, you are blocking the main thread, and the callback you are expecting to happen later will be delivered on the main thread. Since you're blocking the main thread, the callback will never happen. If you want your app to not do anything until that callback is received, you have to set that up another way.
The solution that would be philosophically closest to what you have now would be to spin the main run loop while waiting waiting for the callback, but there are a number of different ways to do it, and that's probably not the way I would choose. That said, if you wanted to do it that way, it might look something like this:
#implementation AppDelegate
{
BOOL didRegisterCalled;
NSData* token;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[application registerForRemoteNotifications];
NSLog(#"registerForRemoteNotifications called. waiting for callback.");
while (!didRegisterCalled)
{
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]];
}
NSLog(#"Register call back happened, and execution resumed");
return YES;
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
didRegisterCalled = YES;
token = [deviceToken copy];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
didRegisterCalled = YES;
token = nil;
}
#end
The simplest way would be to split your startup tasks into a separate method and have the callback call that method. (FWIW, this is probably the approach I would choose.) That might look like this:
#implementation AppDelegate
{
BOOL didRegisterCalled;
NSData* token;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSLog(#"Calling registerForRemoteNotifications and deferring the rest of app startup.");
[application registerForRemoteNotifications];
return YES;
}
- (void)theRestOfTheAppStartupProcess
{
NSLog(#"Finishing app startup now that registration has happened.");
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
token = [deviceToken copy];
if (!didRegisterCalled)
{
didRegisterCalled = YES;
[self theRestOfTheAppStartupProcess];
}
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
token = nil;
if (!didRegisterCalled)
{
didRegisterCalled = YES;
[self theRestOfTheAppStartupProcess];
}
}
#end
Another way might be to set up a dispatch queue, suspend it, and then have the callback resume it. That might look like this:
#implementation AppDelegate
{
dispatch_queue_t appStartupQueue;
NSData* token;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
appStartupQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
dispatch_suspend(appStartupQueue);
dispatch_set_target_queue(appStartupQueue, dispatch_get_main_queue());
dispatch_async(appStartupQueue, ^{
[self theRestOfTheAppStartupProcess];
});
NSLog(#"Calling registerForRemoteNotifications and deferring the rest of app startup.");
[application registerForRemoteNotifications];
return YES;
}
- (void)theRestOfTheAppStartupProcess
{
NSLog(#"Finishing app startup now that registration has happened.");
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
token = [deviceToken copy];
if (appStartupQueue)
{
dispatch_resume(appStartupQueue);
appStartupQueue = nil;
}
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
token = nil;
if (appStartupQueue)
{
dispatch_resume(appStartupQueue);
appStartupQueue = nil;
}
}
#end
But what you have currently is going to produce a deadlock.
I'm receiving notifications using this method:
- (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"reloadComments" object:nil];
}
That trigger this method:
- (void) reloadComments:(NSNotification *)notification{
NSDictionary* dict = [notification.userInfo objectForKey:#"commentNotification"];
NSString* video_id = [[[dict objectForKey:#"aps"] objectForKey:#"custom"] objectForKey:#"data"];
NSData* cData = [video_id dataUsingEncoding:NSUTF8StringEncoding];
NSError *errorJson2;
NSMutableDictionary *response = [NSJSONSerialization JSONObjectWithData:cData options:kNilOptions error:&errorJson2];
int number = [[commentsDictionary objectForKey:[NSString stringWithFormat:#"%#", [response objectForKey:#"video_id"]]] intValue];
number += 1;
[commentsDictionary setObject:[NSNumber numberWithInt:number] forKey:[NSString stringWithFormat:#"%#", [response objectForKey:#"video_id"]]];
}
I'm parsing the result and incrementing the number. This works correctly when i launch the app, in my device, through XCode. If i send 5 push notifications the number is 5.
If i do the same procedure without launching the app through XCode, the number is not correctly incremented.
Anyone has any experience with this and can point me in the right direction?
I added the completionHandler below and it started working.
- (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
[self parsePushNotifications:userInfo];
completionHandler(UIBackgroundFetchResultNewData);
}