Monday, April 9, 2012

Push Notifications and Urban Airship

I have been considering putting Push Notifications into Picross HD for some time now.  This weekend, I decided to take the plunge and give it a try.  I will try to cover some of the hiccups and things that I have been trying.

Why Push Notifications

Ken and I aren't very good at the marketing side of things, but everyone I talk with about marketing asks if we use push notifications.  I finally decided there must be something to this so let's give it a try.

There are a couple of uses for push notifications that I would like to try:
  1. Notify Picross HD users of new puzzle packs being available.  We plan on releasing one new puzzle pack a month, and we would like to notify users when they are available.
  2. Notify users about our other puzzle games.  This one makes me a bit nervous as I don't wont to spam our users, but I also think many Picross HD users would also enjoy some of our other puzzle games.  I'm figuring to give it a try and see what kind of response we get.
I would be interested in hearing what you use push notifications for or what you like or dislike about push notification uses.

Urban Airship

One thing about push notifications is that you need a service to bridge between you and Apple's push notifications servers.  I considered setting up my own server for this, but I decided to go with Urban Airship for now.  For the level of service I need, their service would be "free" and seemed to be the easiest way to get started with Push Notifications.

Getting Started

Urban Airship has a good getting started guide for push notification.  However, I do have a couple of tips.
Tip 1:  You will need to regenerate your provisioning profiles after you enable push notifications for your existing App Id.  If you don't, you will get an error when trying to registerForRemoteNotificationTypes as the provisioning profiles needs to be configured for push notifications.
Tip 2: The getting started guide walks through the steps of using a AirshipConfig.plist to switch between production and development for the notification servers.  I don't like this technique as it is too error prone.  It's too easy to forget how you have it configured.  They suggest using build scripts to manipulate this file, but I don't like that from a maintenance perspective plus I didn't find a quick example of how to do this.  Fortunately, you can do this programmatically instead of using the AirshipConfig.plist.  Here is an example of how taken from the Urban Airship forums (but modified a bit by me). 

- (void)setupUrbanAirshipPushNotificationsWithLaunchOptions:(NSDictionary *)launchOptions
{
    // Set passed in launch options
    NSMutableDictionary *takeOffOptions = [[NSMutableDictionary alloc] init];
    [takeOffOptions setValue:launchOptions forKey:UAirshipTakeOffOptionsLaunchOptionsKey];

    // Create and set dictionary for AirshipConfig values
    NSMutableDictionary *airshipConfigOptions = [[NSMutableDictionary alloc] init];
    [takeOffOptions setValue:airshipConfigOptions forKey:UAirshipTakeOffOptionsAirshipConfigKey];

    [airshipConfigOptions setValue:@"Application Key" forKey:@"DEVELOPMENT_APP_KEY"];
    [airshipConfigOptions setValue:@"Application Secret" forKey:@"DEVELOPMENT_APP_SECRET"];
    
    [airshipConfigOptions setValue:@"Application Key" forKey:@"PRODUCTION_APP_KEY"];
    [airshipConfigOptions setValue:@"Application Secret" forKey:@"PRODUCTION_APP_SECRET"];

    [UAirship setLogging:YES];

    #ifdef PRODUCTION_PUSH_NOTIFICATIONS
        [airshipConfigOptions setValue:@"YES" forKey:@"APP_STORE_OR_AD_HOC_BUILD"];
    #else
        #warning PUSH NOTIFICATIONS SET TO DEVELOPMENT
        [airshipConfigOptions setValue:@"NO" forKey:@"APP_STORE_OR_AD_HOC_BUILD"];
    #endif
    
    
    [UAirship takeOff:takeOffOptions];
    
    [[UAPush shared] setDelegate:self];
    [[UAPush shared] resetBadge];   //zero badge on startup
    [[UAPush shared] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge |
                                                         UIRemoteNotificationTypeSound |
                                                         UIRemoteNotificationTypeAlert)];
} 

This can be called from within application:didFinishLaunchingWithOptions.
Tip 3: In order to test notifications you can use the command line utility curl.  However, I ended up using an app called RESTed available on the Mac App Store.  Its UI could be a bit better, but I was able to edit the Push payload easier in RESTed then trying to do it on the command line.  I also found that with curl the payload didn't like being split on multiple lines using line continuations.
Tip 4: Add the application:didFailToRegisterForRemoteNotificationsWithError to your app delegate.  The guide doesn't instruct you to do this, but if something goes wrong during device registration, its very handle to have this method log the error information.

Processing Push Notifications

When processing push notifications it's up to the App to figure out what to do with the notifications (including showing them).  The UAPush class from Urban Airship helps manage push notifications by handling things like badges and parsing of push notifications.   Setting up UAPush is easy, as you can see in the example code above.

In order for UAPush to parse the notifications, you need to pass them to UAPush via its handleNotification:applicationState method.


This was taken from example code for the UAPush.  However, I modified it to remove the applicationState check.   application:didReceiveRemoteNotification can be called before the application has been made active.  However, I still want to display an alert even if we are becoming active.  From what I can tell the method only gets called while the application is being "launch" or "restored", it doesn't get called while the app is backgrounded.  However, if handleNotification:applicationState is called with an inactive state, it won't call its delegate parsing methods.  Which isn't what I wanted.  I want the App to display a notification if the notification is clicked on outside the app.

I still looking into understanding the implications of this, but I think for my purposes (of displaying an alert) it is fine.

Localization Implications

Picross HD is localized into 5 languages so when sending notifications, I would like them to be localized.  However, I haven't figured out how to send a localized push alert  (see "loc-key") without having the text pre-localized and stored in the application's Localizable.strings file(s).

While you can send custom push notifications that are localized, iOS won't know how to display them.  For notifications within the App that isn't an issue because the App has control of that, but when the app is in a background iOS can only display "toast" messages for types it knows about such as alerts.

This means you have to predetermine the messages you might want to send if you want them localized.  You can also supply message arguments to fill in placeholders in the message such as %@.  However, I don't believe the arguments are localized.

Conclusion

I'm still working on the implementation and trying to figure out the types of messages and workflows I want to do.  For example, if I do an announcement of a new puzzle pack some people may be on the old version that doesn't have that puzzle pack available and some may be on the new version that has the new puzzle pack.  In the first case, I may want to provide an option for the use to update to the latest version, in the later case I may want to take them to the new puzzle pack so they can try it out.

I hope this was useful.  Please try out Picross HD and our other puzzle games for iOS.

- Tod