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.
Related
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.
i recently set up push notifications for my app using Parse. I have been able to get the notifications and the data sent but now i want to get the channel to which the notification has been sent. Is there any way to do so in ios?
Have you checked this guide? It's really helpful.
https://www.parse.com/tutorials/ios-push-notifications
Here's a quick summary. Create development and production certificates and then attach them to your developer account for the app that you want to be able to send pushes to. After attaching these certificates redownload them, change them to .p12 files w Keychain Access and then upload them to Parse.com. In Xcode make sure to go into your account via preferences and refresh it so you'll have an updated provisioning profile. I'd do this in the stable version of Xcode and not the beta.
Finally after doing all that you can start coding. In your app delegate attach this code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
UIUserNotificationType userNotificationTypes = (UIUserNotificationTypeAlert |
UIUserNotificationTypeBadge |
UIUserNotificationTypeSound);
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:userNotificationTypes
categories:nil];
[application registerUserNotificationSettings:settings];
[application registerForRemoteNotifications];
...
}
Please note that the following code demonstrates how to add a user to a channel... This is Parse's default code and I actually suggest tweaking it a bit. They set it up so that your channels will be reset to global every time the application is launched. I'm not sure that you even need to invoke the method "registerUserNotificationSetting" at all after attaching the device's token to the backend. Take not this method will generate an API request...
- (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];
// This is Parse's default code
currentInstallation.channels = #[ #"global" ];
// I would change it to something like this
if (currentInstallation.channels.count == 0) {
currentInstallation.channels = #[ #"global" ];
}
[currentInstallation saveInBackground];
}
Finally the last bit of code just deals with what the application does if it receives a notification while in the foreground:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
[PFPush handlePush:userInfo];
}
Good luck!
When you send the notification just add the channel name to the notifications data.
For example:
PFPush *push = [[PFPush alloc] init];
[push setChannel:#"my_channel"];
NSDictionary *data = #{#"alert":#"Hi there.",#"channel":#"my_channel"};
[push setData:data];
[push sendPushInBackground];
When you receive the notification you can simple get the channel from the payload/userInfo:
NSString *channel = payload[#"channel"];
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
}];
}
Okay so I have an app where I want to implement push notifications. The problem is that I want to use the users username to help identify which device they are sending the notifications too. But when it is called in the delegate sometimes the users are not logged in yet so the app crashes. The code is in this method
- (void)applicationDidBecomeActive:(UIApplication *)application
{
PFInstallation *currentInstallation = [PFInstallation currentInstallation];
currentInstallation[#"installationUser"] = [[PFUser currentUser]username];
// here we add a column to the installation table and store the current user’s ID
// this way we can target specific users later
// while we’re at it, this is a good place to reset our app’s badge count
// you have to do this locally as well as on the parse server by updating
// the PFInstallation object
if (currentInstallation.badge != 0) {
currentInstallation.badge = 0;
[currentInstallation saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (error) {
// Handle error here with an alert…
}
else {
// only update locally if the remote update succeeded so they always match
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
NSLog(#"updated badge");
}
}];
}
}
So I need this NOT to be called if the user has not logged in yet but then I want it to be called every time. I am getting confused about how to do this. Let me know if you have any ideas, thanks!
You wouldn't normally call this in applicationDidBecomeActive I see you've read up on the docs a little bit but it's real easy:
if (![PFUser currentUser]) { // No user logged in : this is all you need because in applicationDidRegistreForRemoteNotificationsWithDeviceToken should have set the user or installation ID already
//Enter data
} else {
// User logged in
}
NOTE : This is best to be used in your main view controllers viewDidAppear method. Review their documentation for future guidance : https://parse.com/tutorials/login-and-signup-views
Hi I have an app that I have set up with push notifications and they are working. My last step is that when a user opens the app and is asked if they want to allow push notification, I want to set a pointer from that user to the installation id associated with that phone. I can do that using this
PFInstallation *currentInstalation = [PFInstallation currentInstallation];
NSString *installationObjectId = currentInstalation.objectId;
[currentUser setObject:installationObjectId forKey:#"installationString"];
[currentUser setObject:currentInstalation forKey:#"installation"];
Here is a pic of part of my user class to clarify
But I don't want to make this save every time the user opens the app, I just want to do it the once if it has not been set yet. So I was going to use this if statement to check if it had been set yet
if (![currentUser[#"installationString"] isEqual:installationObjectId]) {
//save it here
}
But the problem comes if the user taps, "don't allow push notifications" because then there is no installation object set, so the installation object for that phone/ user is null and the above if statement gives the error below
*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: 'Can't use nil for keys or values on PFObject. Use NSNull for values.'
And the app fails. Is there another/ better way to check if the pointer has been set, so that if the user taps "don't allow" and then reopens the app it won't quit out.
Thanks for the help in advance I really appreciate it!!!
EDIT
APP DELAGTE CODE
if ([application respondsToSelector:#selector(registerUserNotificationSettings:)]) {
UIUserNotificationType userNotificationTypes = (UIUserNotificationTypeAlert |
UIUserNotificationTypeBadge |
UIUserNotificationTypeSound);
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:userNotificationTypes
categories:nil];
[application registerUserNotificationSettings:settings];
[application registerForRemoteNotifications];
} else {
// Register for Push Notifications before iOS 8
[application registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge |
UIRemoteNotificationTypeAlert |
UIRemoteNotificationTypeSound)];
}
- (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 saveInBackground];
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
[PFPush handlePush:userInfo];
}
And I have it set this way because I have a cloud function that send pushes to user and and get a pointer from the user to the installation id to send the push, maybe I could consider flipping that?
And if the user clicks don't allow. Doesn't Parse.com not save an installation id for that device?
Thanks
This should help PFInstallation *currentInstalation = [PFInstallation currentInstallation];
NSString *installationObjectId = currentInstalation.objectId;
[currentUser setObject:installationObjectId forKey:#"installationString"];
[currentUser setObject:currentInstalation forKey:#"installation"];
SWIFT:
var newUser = PFUser()
var currentInstallation = PFInstallation.currentInstallation()
let installationObjectId: NSString = currentInstallation.installationId
newUser.setObject(installationObjectId, forKey: "installationString")
newUser.setObject(currentInstallation, forKey: "installation")
newUser.saveInBackgroundWithBlock { (success, error) -> Void in
if error == nil {
print("success")
} else {
print(error)
}
}