I'm currently working on project that allows user to receive push notifications whenever there is something new on the user account. I'm using Parse as my push notifications service. I'm having no problem until recently our server starting to receive empty token device on every push notification registration, this problem is not always happening. So when I tried the app on my device it just run as it should but when my app tested on our client device , our server receive an empty token device for that client user. How can this happen? How can I fix this? And how is the best practice to get and set the device token?
Here is my code in appdelegate:
- (void) application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
const unsigned *tokenBytes = [deviceToken bytes];
NSString *token = [NSString stringWithFormat:#"%08x%08x%08x%08x%08x%08x%08x%08x",
ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];
//function for saving device token to server
[[ASEngine defaultEngine] setCurrentDeviceToken:token];
if([[ASEngine defaultEngine] currentCredential] != nil) {
[[ASEngine defaultEngine] webStoreDeviceToken:token];
}
//save current instalation to parse
PFInstallation *currentInstallation = [PFInstallation currentInstallation];
[currentInstallation setDeviceTokenFromData:deviceToken];
[currentInstallation saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
NSLog(#"Error e current installation: %#", error);
}];
//save device token locally
[[NSUserDefaults standardUserDefaults] setObject:deviceToken forKey:#"deviceToken"];
}
An empty token wont ever be generated,
iOS provides didFailToRegisterForRemoteNotificationsWithError method which is being probably called in your case, please make sure to check that for any errors in token creation.
Sometimes it happens when your device is not connected to internet.
Make sure your device is connected to internet.
Related
I am encountering a weird behavior of the PFInstallation of my iOS App. On every app launch I am registering the device to receive push notifications and calling the below code on the method didRegisterForRemoteNotificationsWithDeviceToken:
PFInstallation *currentInstallation = [PFInstallation currentInstallation];
[currentInstallation setDeviceTokenFromData:deviceToken];
NSLog(#"%#", currentInstallation );
[currentInstallation saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
NSLog(#"currentInstallation saved" );
}
if (error) {
NSLog(#"currentInstallation NOT saved" );
}
}];
When I install the app on my iphone the installation is saved correctly with its deviceToken. I am absolutely sure that some days ago i was manually deleting the deviceToken through the Parse Dashboard (to try out things) then on app re-launch the device token was saved correctly again. Same thing for some channels I was using. Today the deviceToken is not being saved, nor the channels. The saveInBackgroundWithBlock succeeds but the deviceToken field is empty. The NSLog of the currentInstallation contains the deviceToken, but it's not saved. While trying to understand the reason for this behavior i found out that if I add the line currentInstallation.deviceToken = #""; just after PFInstallation *currentInstallation = [PFInstallation currentInstallation]; then the deviceToken gets saved correctly. Consume Parse guru explain to me why this is happening and why some days ago it wasn't? I recently upgraded the Parse Framework on this app, can that be the reason?
I have following scenario.
User A sends message to User B in foreground -- this is working
Send push notification to User B, when app is in background, from 'Messages' console -- this is working
I want to send notification alert to User B when app is in background. I read that this is done automatically by quickblox, but is not happening for me.
I have followed instructions on this link
I am using 'Starter' account in development mode. Do we need account with ' server side history' for this functionality?
Edit 1:
Clarification: I want to send 'automatic push notifications for offline user' and not notification from app. I am also sending 'save_to_history' flag as mentioned on the link.
for Sending push notification use below code
-(void)applicationDidEnterBackground:(UIApplication *)application
{
[self sendMessageNotification:#"Hello Push notification" :1234 ];
}
-(void)sendMessageNotification:(NSInteger)recipientID message:(NSString*)message
{
isSentPushNotification = YES;
//[self sendPushNotificationToUser:message ids:#"1" audioFileName:#"default"];
NSMutableDictionary *payload = [NSMutableDictionary dictionary];
NSMutableDictionary *aps = [NSMutableDictionary dictionary];
[aps setObject:#"default" forKey:QBMPushMessageSoundKey];
[aps setObject:message forKey:QBMPushMessageAlertKey];
[aps setObject:#"1" forKey:QBMPushMessageAlertLocArgsKey];
[payload setObject:aps forKey:QBMPushMessageApsKey];
QBMPushMessage *pushMessage = [[QBMPushMessage alloc] initWithPayload:payload];
// Send push to users with ids 292,300,1395
[QBRequest sendPush:pushMessage toUsers:[NSString stringWithFormat:#"%lu",(long)recipientID]successBlock:^(QBResponse *response, QBMEvent *event) {
NSLog(#"Successfully dilivered push notification");
} errorBlock:^(QBError *error) {
NSLog(#"Fail to diliver push notification %#",error);
}];
}
But you should first Subscribe User to receive Push Notifications
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
// Register subscription with device token
[QBRequest registerSubscriptionForDeviceToken:deviceToken successBlock:^(QBResponse *response, NSArray *subscriptions) {
// Registration succeded
} errorBlock:^(QBError *error) {
// Handle error
}];
}
for more detail please have a look of this
Found the problem.
We have to logout from the the chat when the app enters background.
I think, this might be required to let server know that we are actually offline and it should send us push notification instead. (just a guess!!)
I think logout thing was mentioned in documentation as well but I didn't know that it was so important.
I am building an app that will implement a Parse backend to send push notifications. Users will be able to send other users a message contained in a push notification. I have been playing around with this and when I send it registers on the Parse website fine but only about 50% of the messages sent are being received on the device. Does anyone else have this problem? I know Push Notifications are not guaranteed but a 50% success rate? Any ideas?
Thanks
code below:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[Parse setApplicationId:#"***"
clientKey:#"****"];
UIUserNotificationType userNotificationTypes = (UIUserNotificationTypeAlert |
UIUserNotificationTypeBadge |
UIUserNotificationTypeSound);
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:userNotificationTypes
categories:nil];
[application registerUserNotificationSettings:settings];
[application registerForRemoteNotifications];
return YES;
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
// Store the deviceToken in the current installation and save it to Parse.
PFInstallation *currentInstallation = [PFInstallation currentInstallation];
[currentInstallation setDeviceTokenFromData:deviceToken];
currentInstallation.channels = #[ #"global" ];
[currentInstallation saveInBackground];
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
[PFPush handlePush:userInfo];
}
- (void)notifyFriendAtIndex:(NSInteger)index completionHandler:(userCompletionHandler)handler
{
PFQuery *pushQuery = [PFInstallation query];
[pushQuery whereKey:#"deviceType" equalTo:#"ios"];
NSString *pushMessage = [NSString stringWithFormat:#"From %# HI!", self.currentUser.username];
NSDictionary *pushData = #{#"alert": pushMessage, #"badge": #0, #"sound": #"notify.wav"};
PFPush *push = [[PFPush alloc] init];
[push setQuery:pushQuery];
[push setData:pushData];
[push sendPushInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (error) {
NSLog(#"Error: %# %#", error, [error userInfo]);
handler(NO, error);
}
else {
handler(YES, nil);
}
}];
}
I have not experienced any major problems in the past with Parse's Push Notification delivery. One of my apps has about 32,000 registered devices and seems to get near 100% delivery.
I also have a couple of chat apps that use Parse as the messaging and push back-end. Users can subscribe to a particular chat room and get Push Notification through Segmentation. One of those apps just launched and so far the push notifications are coming through 100%. The only thing I've noticed is a slight delay sometimes. Maybe a minute or two, but I think that could be because of a slow wifi connection.
I also use PushWoosh for general Broadcast Push Notifications. They are great, but sometimes PushWoosh takes longer to deliver. Parse is usually faster so the bottom line is I think Parse's Push Notifications are reliable.
In my experience, i concluded that most is due the main push notification deliverer ( Apple for iOS, Google for Android ). I'm saying this because we have an app in Parse that send push notification on both iOS and Android, and some days we don't receive any push notification, some others a few or all. All this without changing any implementation. In past, i used another push notification service called Urban Airship (that rely on the proper push notification deliverers), and there was the same problem, some days "almost" ok, some other no work.
Just check if your testing device are correctly registered in the "Installation" table with proper deviceToken set. We send the push notification server side through channels. I write here the code maybe it could help you:
var dataStruct ={
alert: "Hi there!",
my_additional_info: "normal chat message"
};
Parse.Push.send({
channels: ["your_custom_registered_installation_channel_device"],
data: dataStruct
}).then(function() {
promise.resolve();
}, function(error) {
promise.reject(error);
});
So normally it depends by the day, but if you have this statistic (50%) maybe the installation is not always registered/updated when your iPhone register for push notification
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
PFInstallation *currentInstallation = [PFInstallation currentInstallation];
// Save/update device tocken
[currentInstallation setDeviceTokenFromData:deviceToken];
// store your channel
[currentInstallation setChannels:#["channel_123"]];
[currentInstallation saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
// Do your stuff
}];
}
I recently released my iOS game to the App Store and was about to send my first Push Notification when I noticed that the # Recipients (= # Installations) is about 1/3 the # Users (on Parse dashboard).
I released using Parse 1.2.21 (and have subsequently upgraded to 1.4.1, but not released yet).
I am skeptical that 2/3 of my users have opted out of notifications.
note: I did not implement didFailToRegisterForRemoteNotificationsWithError (now added for next release).
The only theory I have is as follows:
When I released to App Store, I was unaware of the "Released to production" switch (released w/ NO).
A week later, I noticed this button and switched it to YES.
Yesterday, I tested push notifications and verified that it was sent to a good sampling of the installs.
THEORY: Before I enabled "Released to production" the Development APN was being used and thus failed.
Better ideas on why #Installations is 1/3 of #Users? :-)
Here's my code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
// Parse: Enable auto-user.
[PFUser enableAutomaticUser];
[[PFUser currentUser] incrementKey:#"runCount"];
// Save the user to force a round trip w/ Parse (and obtain objectId if not already).
[[PFUser currentUser] saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
NSLog(#"AppDelegate::application: Save PFUser succeeded: %#, objectId: %#", [PFUser currentUser], [PFUser currentUser].objectId);
} else {
// Log details of the save failure.
if ([error code] == kPFErrorObjectNotFound) {
// This user does not exist.
NSLog(#"AppDelegate::application: RARE CASE: The current PFUser does not exist on Parse! This is probably due to us deleting the user to force a reset or a mistake. Logging this user out... lazily creating new PFUser...");
[PFUser logOut];
} else {
// Other errors.
NSLog(#"AppDelegate::application: RARE CASE: Saving the PFUser currentUser FAILED! currentUser: %#, objectId: %#.... saving eventually in attempt to re-try...", [PFUser currentUser], [PFUser currentUser].objectId);
// Save eventually to ensure it gets saved.
[[PFUser currentUser] saveEventually];
}
NSString *codeString = [NSString stringWithFormat:#"Save PFUser (app init), code:%d", [error code]];
[PFAnalytics trackEvent:#"error" dimensions:#{ #"code": codeString }];
}
}];
...
// Parse: Register for push notifications
if ([application respondsToSelector:#selector(isRegisteredForRemoteNotifications)])
{
// iOS 8 Notifications
UIUserNotificationSettings *settings =
[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert |
UIUserNotificationTypeBadge |
UIUserNotificationTypeSound
categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
[[UIApplication sharedApplication] registerForRemoteNotifications];
} else {
// iOS < 8 Notifications
[application registerForRemoteNotificationTypes: UIRemoteNotificationTypeBadge |
UIRemoteNotificationTypeAlert |
UIRemoteNotificationTypeSound ];
}
}
Successful:
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)newDeviceToken {
// Store the deviceToken in the current installation and save it to Parse.
PFInstallation *currentInstallation = [PFInstallation currentInstallation];
[currentInstallation setDeviceTokenFromData:newDeviceToken];
[currentInstallation saveInBackground];
}
Fail:
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
// An error occured during register push notifs.
NSLog(#"ERROR:didFailToRegisterForRemoteNotificationsWithError: error: %d, %#", error.code, error.description );
NSString *codeString = [NSString stringWithFormat:#"Register Push Notifications, code:%d", [error code]];
[PFAnalytics trackEvent:#"error" dimensions:#{ #"code": codeString }];
}`
I went through the same situation and can speak to it directly. Yes, your 'Released to Production' problem is the most likely culprit.
It also takes time for users to opt in if they aren't using the app every day/week. When we added Push Notifications, our ratio of Installations:Users was around 70%. The problem was that it was only asking for users to add push after their next usage of the app, and some users might go days or weeks before they open the app again and get asked to register (and create their Installation object). Now that we have had the system up and running with pushes for 3 months, the ratio is over 95%; almost all users have opted in for push.
Just be patient and eventually your coverage will go up as users :)
All of my theories were wrong. :-)
I am now 99% certain that the 2/3 of users were indeed denying approval for push notifications because I was asking immediately during app init (i.e. before the new user had any loyalty or trust established with my app).
And, considering that you really only get ONE CHANCE to ask, this may be an important decision for your app.
After reading a lot of advice on best practices, the consensus appears to be:
Ask the user to approve push notifications at a time that makes sense to the user or after something good has happened. Ideally, you make a good case for why they want to approve. Remember that you only get ONE CHANCE (technically, they can change this setting, but is highly unlikely).
Consider using a two-phase modal: The first modal is a custom modal created by you and asks if they would approve push notifications (plus any motivation and trust you can establish). If they tap YES to your modal, THEN pop the Apple push notification request (ios8:registerUserNotificationSettings,
Several folks have shown data that the two-step modal increases conversion rates significantly.
My app registers the account at login in my server to enable push notification for a chat. Yet I haven't implemented yet the unregistration of the account at logout, so in this moment if I do the login with 2 accounts in the same device it can take the notification of both the accounts. At the same time, my notification center has a POST service which unregisters the 'login_name+ device token' from receive notification center. Where should I call it? Do I have to use unregisterForRemoteNotifications? I just want to unregister the account+Device token from push notification, not to disable the entire app notification forever.
Can I save my device token on didRegisterForRemoteNotificationsWithDeviceToken function like
$ [[NSUserDefaults standardUserDefaults] setObject:hexToken forKey:DEVICE_KEY];
and then, at logout, call my POST function "removeDeviceToken" like
NSString *deviceToken = [userDefaults objectForKey:DEVICE_KEY];
if(deviceToken != NULL){
[self.engine removeDeviceToken:deviceToken];
}
You can easily enable and disable push notifications in your application by calling
To register, call: registerForRemoteNotificationTypes:
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
To unregister, call: unregisterForRemoteNotificationTypes:
[[UIApplication sharedApplication] unregisterForRemoteNotifications];
For check use
Enable or Disable iPhone Push Notifications try this code
UIRemoteNotificationType types = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
if (types == UIRemoteNotificationTypeNone)
// Yes it is..
I'm not sure if i got it correctly, but if you don't want to disable push notifications for the app, then you should't call the unregisterForRemoteNotifications. What you can do is, when the user taps the logout button, you can make a logout request to your server, which then removes the notificationID from that account, and after the logout request is completed, you just perform the logout locally (update UI etc).
More info about comment:
Yes, first of all, you should call registerForRemoteNotificationTypes method at every launch, because device token can change. After the delegate method
- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken
is called, you can get the device token and save it to NSUserDefault. That way when the user logs in, you can get the up-to-date device token (if changed), and send it to your server to be added to that account.
So the code might look like this
- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
NSString *newToken = [devToken description];
newToken = [newToken stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#"<>"]];
newToken = [newToken stringByReplacingOccurrencesOfString:#" " withString:#""];
NSString *notificationID = [newToken description];
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs setObject:notificationID forKey:#"notification_id"];
[prefs synchronize];
}
So, now when the user logs in, just get the notification ID and send it to your server
- (IBAction)userTappedLoginButton {
// Make your login request
// You can add the notification id as a parameter
// depending on your web service, or maybe make
// another request just to update notificationID
// for a member
NSString *notificationID = [[NSUserDefaults standardUserDefaults] objectForKey:#"notification_id"];
...
...
}
With Swift:
To Register,
UIApplication.shared.registerForRemoteNotifications()
To unregister,
UIApplication.shared.unregisterForRemoteNotifications()
In general it is a bad idea to unregisterForRemoteNotifications after logout and reregister after login. The reason is simple: if the user logins with another account and you don't specifically check for token overlapping in server, the user will start receiving double notifications.