Messaging

In-App Messaging

Leanplum comes with a number of in-app messaging templates, including alerts, confirmation screens, popups, and full-screen interstitials. They are open source, so you can customize them however you want, and you can even create your own templates.

To enable in-app messaging, you'll need to integrate the Leanplum SDK and run your app at least once in development mode.

On the dashboard, you can set in-app messages to automatically appear when certain events occur or when the app starts. These events must be tracked with our SDK. There's no additional coding needed.

Push Notifications

To enable Push Notifications on iOS, you need to upload your certificates to Leanplum and register for remote notifications in your app.

Setup

  1. Login to the iOS provisioning portal.
  2. In the Identifiers > App IDs, select your app, click Edit, and enable Push Notifications.
  3. Click Create Certificate for each of the Development and Production certificates and follow the onscreen instructions. You should not reuse existing certificates so that we can track delivery failures properly.
  4. Download your new certificate files from your browser. Open the files on your computer, which will launch Keychain.
  5. In Keychain, select the new certificates, expand them to view the private key, and then right click to export them as .p12 files. You must enter a password.
  6. In Leanplum, go to your app's Keys & Settings (App Settings > {Your app} > Keys & Settings). Under Push Notifications, upload your .p12 files to Leanplum and enter your passphrase from step 5 above.
  7. Configure your app to use push notifications in your app delegate's applicationDidFinishLaunching method (you may choose any combination of formats; this is just an example):
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    id notificationCenterClass = NSClassFromString(@"UNUserNotificationCenter");
    if (notificationCenterClass) {
        // iOS 10.
        SEL selector = NSSelectorFromString(@"currentNotificationCenter");
        id notificationCenter =
        ((id (*)(id, SEL)) [notificationCenterClass methodForSelector:selector])
        (notificationCenterClass, selector);
        if (notificationCenter) {
            selector = NSSelectorFromString(@"requestAuthorizationWithOptions:completionHandler:");
            IMP method = [notificationCenter methodForSelector:selector];
            void (*func)(id, SEL, unsigned long long, void (^)(BOOL, NSError *__nullable)) =
            (void *) method;
            func(notificationCenter, selector,
                 0b111, /* badges, sounds, alerts */
                 ^(BOOL granted, NSError *__nullable error) {
                     if (error) {
                         NSLog(@"Leanplum: Failed to request authorization for user "
                               "notifications: %@", error);
                     }
                 });
        }
        [[UIApplication sharedApplication] registerForRemoteNotifications];
    } else if ([[UIApplication sharedApplication] respondsToSelector:
                @selector(registerUserNotificationSettings:)]) {
        // iOS 8-9.
        UIUserNotificationSettings *settings = [UIUserNotificationSettings
                                                settingsForTypes:UIUserNotificationTypeAlert |
                                                UIUserNotificationTypeBadge |
                                                UIUserNotificationTypeSound categories:nil];
        [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
        [[UIApplication sharedApplication] registerForRemoteNotifications];
    } else {
        // iOS 7 and below.
        #pragma clang diagnostic push
        #pragma clang diagnostic ignored "-Wdeprecated-declarations"
        [[UIApplication sharedApplication] registerForRemoteNotificationTypes:
        #pragma clang diagnostic pop
         UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge];
    }

    // Other code.
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

    //iOS-10
    if #available(iOS 10.0, *){
        let userNotifCenter = UNUserNotificationCenter.current()

        userNotifCenter.requestAuthorization(options: [.badge,.alert,.sound]){ (granted,error) in
            //Handle individual parts of the granting here.
        }
        UIApplication.shared.registerForRemoteNotifications()
    }
    //iOS 8-9
    else if #available(iOS 8.0, *){
        let settings = UIUserNotificationSettings.init(types: [UIUserNotificationType.alert,UIUserNotificationType.badge,UIUserNotificationType.sound],
                                        categories: nil)
        UIApplication.shared.registerUserNotificationSettings(settings)
        UIApplication.shared.registerForRemoteNotifications()
    }
    //iOS 7
    else{
        UIApplication.shared.registerForRemoteNotifications(matching:
            [UIRemoteNotificationType.alert,
             UIRemoteNotificationType.badge,
             UIRemoteNotificationType.sound])
    }
    //Other code.
}

Note: If you are implementing application:didReceiveRemoteNotification:fetchCompletionHandler in your code, you should call the completion handler yourself.

Push categories

iOS 8 supports push notification categories, which allow you to provide interactivity to your notifications with custom actions. To use this, define categories in your code (watch Apple's video tutorial). Leanplum allows you to choose the category of your notifications from the dashboard, track each custom action, and even define the logic for what happens on each action. To use categories with Leanplum, you'll need additional calls to Leanplum to handle custom actions in your app delegate. If you call [Leanplum handleActionWithIdentifier], do not call completionHandler as Leanplum will call this internally when it's ready.

- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier
    forRemoteNotification:(NSDictionary *)notification completionHandler:(void (^)())completionHandler
{
    [Leanplum handleActionWithIdentifier:identifier
                   forRemoteNotification:notification
                       completionHandler:completionHandler];
}

- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification completionHandler:(void (^)())completionHandler
{
    [Leanplum handleActionWithIdentifier:identifier
                    forLocalNotification:notification
                       completionHandler:completionHandler];
}

It's a good practice to show additional information or navigate to the correct part of your app when users open your push notifications. You can do that using Leanplum's in-app messaging, which integrates nicely with push notifications. There's no additional coding needed.

Badge counts

The app icon badge is meant to display a count of unread items that await the user's attention. By design, this relies entirely on a remote server to be the system of record; each push notification can set the count with a badge attribute in the payload. If the value is 0, iOS will clear the count and remove the badge. If a value is not set in the payload, the count remains the same.

For example, this payload sets the badge to 9.

{
    "aps" : {
        "alert" : "You got your emails.",
        "badge" : 9
    },
    "acme1" : "bar",
    "acme2" : 42
}

This payload clears the badge, even if the user does not open the notification. A silent push works the same way, but would not include an alert value.

{
    "aps" : {
        "alert" : "You got your emails.",
        "badge": 0
    },
    "acme1" : "bar",
    "acme2" : 42
}

In the Leanplum Message Composer, you can set the badge value in your push notification to any number. Leaving the field blank will do nothing (i.e. maintain the current badge count), and setting it to zero will clear the count. Unfortunately, you cannot increment the existing badge count directly from Leanplum.

You'll need to add some code to your app to increment and clear the badge count.

To increment the badge whenever a push is received in the background, add the following to didReceiveRemoteNotification in your delegate:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {

  // Only increment badge if in background.
  if(application.applicationState == UIApplicationStateBackground) {
    NSInteger badge = [[UIApplication sharedApplication] applicationIconBadgeNumber];
    [[UIApplication sharedApplication] setApplicationIconBadgeNumber: badge + 1];
  }

  completionHandler(UIBackgroundFetchResultNewData);
}
func application(_ application: UIApplication,
                          didReceiveRemoteNotification userInfo: [AnyHashable : Any],
                          fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {

  // Only increment badge if in background.
  if application.applicationState == .background {
    UIApplication.shared.applicationIconBadgeNumber += 1
  }

  completionHandler(.newData)
}

To clear the count on app start or resume, you could implement the following in your applicationDidBecomeActive delegate method. However, this would clear the count even if they open the app by some means other than opening the push notification.

- (void)applicationDidBecomeActive:(UIApplication *)application {
    // Clear app badge on start or resume.
    [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
}
func applicationDidBecomeActive(_ application: UIApplication) {
    // Clear app badge on start or resume.
    UIApplication.shared.applicationIconBadgeNumber = 0
}

Custom alert sounds

You can set a custom alert sound for a push notification via the Leanplum Message Composer. However, before doing so, you must include the sound file in your app, following Apple's guidelines for file type, length and size. See Preparing Custom Alert Sounds.

Once the file is available in your app, simply enter the filename with extension for the iOS push option sound for the message.

Testing

Once you've set up push notifications, test that it is working properly. Send a push notification to your development devices. Run your app, wait a few seconds, and then press the home button.

Troubleshooting

  1. On the device, verify the app is configured to enable pushes: Settings -> Notification Center -> {Your app name}
  2. Recreate the provisioning profile. See iOS provisioning portal

Location-based Messaging

You can set up geofence and iBeacon regions as criteria to trigger in-app messages and push notifications, right from the dashboard.

To enable this functionality in your app, follow the setup instructions about adding LeanplumLocation or LeanplumLocationAndBeacons to the Podfile or as an additional linked framework. If you did a manual install, add CoreLocation to Build Phases -> Link Binary With Libraries.

Next, add a few keys in your Info.plist file:

  1. Add the key NSLocationWhenInUseUsageDescription: This text will appear to the user when prompting for permissions to enable location monitoring in the foreground on iOS 8+. (Required for in-app messages)
  2. Add the key NSLocationAlwaysUsageDescription: This text will appear to the user when prompting for permissions to enable location monitoring in the background on iOS 8+. (Required for push notifications)
  3. Add the key NSLocationUsageDescription: This text will appear to the user when prompting for permissions to enable location monitoring in the foreground or background on iOS 7. (Optional for in-app messaging and push notifications)
  4. Add the key Required background modes if it does not already exist. Within it, add a background mode App registers for location updates. (Required for push notifications)

Note: iOS limits the number of locations that an app can save to 20; however, our SDK manages this for you by only storing the nearest 20 locations to each user's device at any given time. This way, you can have more than 20 locations saved in Leanplum, but your users will only have the 20 most relevant to them (i.e. the closest).

Region monitoring is available in iOS 7 and higher.

App Inbox

App Inbox is a standalone messaging channel that doesn't require push certificates and can store messages for long periods of time.

To enable Inbox messaging, you'll need to integrate Leanplum iOS SDK version 1.6.0 or higher (previous versions use Newsfeed) in your app. After that, you can instantiate the LPInbox object by calling:

[Leanplum inbox]
Leanplum.inbox()

Getting data from Leanplum

Messages and Inbox data are retrieved from Leanplum on Leanplum.start(). The start method returns data asynchronously, so you should use the provided onChanged callback, which will be called automatically when updates to the inbox are finished loading from Leanplum.

Here is a simple example of how you can use the callback to update the number of unread messages:

[[Leanplum inbox] onChanged:^() {
    unreadCountLabel.text = [NSString stringWithFormat:@"%@", @([[Leanplum inbox] unreadCount])];
}];
Leanplum.inbox().onChanged({
    unreadCountLabel.text = String(Leanplum.inbox().unreadCount())
})

Because messages are only retrieved on start, a user will not receive a new message until they begin a new session. You can, however, use forceContentUpdate to re-sync with Leanplum mid-session, which will fetch any new App Inbox messages, and automatically invoke callbacks you have registered.

forceContentUpdate will also update all of your variables, user attributes, messages and A/B tests, so it can (depending on how your app is setup to work with Leanplum) affect the functionality and UI of your app. For more on forceContentUpdate, including some precautions, see Syncing with Leanplum mid-session.

Inbox message counts

After data is finished loading from Leanplum, you can get the total and unread count of messages using the LPInbox object:

LPInbox *inbox = [Leanplum inbox]; // get the shared Inbox object
NSUInteger count = [inbox count];
NSUInteger unreadCount = [inbox unreadCount];
let inbox: LPInbox = Leanplum.inbox()
let count: UInt  = inbox.count()
let unreadCount: UInt = inbox.unreadCount()

Inbox messages

After data is finished loading from Leanplum, you can use the LPInbox methods allMessages or unreadMessages to get an NSArray of Message objects, either all or just the user's unread messages.

// Use the LPInbox instance inbox to get messages.
NSArray *allMessages = [inbox allMessages];
NSArray *unreadMessages = [inbox unreadMessages];
// Use the LPInbox instance inbox to get messages.
let allMessages: [LPInboxMessage] = inbox.allMessages() as! [LPInboxMessage]
let unreadMessages: [LPInboxMessage] = inbox.unreadMessages() as! [LPInboxMessage]

Alternatively, you could use messagesIds to get an NSArray of all the message IDs, then loop through the returned items and call messageForId with each messageId to fetch each message:

// Use the LPInbox instance inbox to get message Ids.
NSArray *messageIds = [inbox messagesIds];
for(int i = 0; i < [messageIds count]; i++){
  LPInboxMessage *message = [inbox messageForId: messageIds[i]];
  // Do stuff with message.
}
// Use the LPInbox instance inbox to get message Ids.
let messageIds = inbox.messagesIds()
for messageId in messageIds! {
    let message: LPInboxMessage = inbox.message(forId: messageId as! String)
    // Do stuff with message.
}

Message attributes

Once you have the Message object, you can access its title, subtitle, and deliveryTimestamp for display.

LPInboxMessage *message = [[Leanplum inbox] messageForId: messageId];
NSString *title = [message title];
NSString *subtitle = [message subtitle];
NSDate *timestamp = [message deliveryTimestamp];
let message: LPInboxMessage = Leanplum.inbox().message(forId: messageId)
let title: String = message.title()
let subtitle: String = message.subtitle()
let timestamp: Date = message.deliveryTimestamp()

You can also easily test if an LPInboxMessage is read:

if([message isRead]){
  // Do something if the message has been read.
}
if(message.isRead()){
  // Do something if the message has been read.
}

Message image

We also support sending an image with an Inbox message, although the feature is currently in beta (contact your Customer Success Manager for details on getting whitelisted).

If you haven't disabled automatic image downloads (image prefetching), you can get the image path on the device:

UIImage *image = [UIImage imageWithContentsOfFile:[message imageFilePath]];
let image: UIImage = UIImage.init(contentsOfFile: message.imageFilePath())

If you want to disable automatic image downloads on sync (image prefetching), you can do so with the LPInbox method disableImagePrefetching before calling start.

[[Leanplum inbox] disableImagePrefetching];
[Leanplum start];
Leanplum.inbox().disableImagePrefetching()
Leanplum.start()

If you do so, you'll need to get the image URL for each message using the LPInboxMessage method imageURL, and then download the files yourself later, or use a third-party solution to manage this for you. If prefetch is enabled or the image has already been downloaded, imageURL will return the URI to the file on the device.

// Get the Image URL for a single LPInboxMessage.
NSURL *url = [message imageURL];
// Use the URL to download the image.
// Get the Image URL for a single LPInboxMessage.
let url: URL = message.imageURL()
// Use the URL to download the image.

Handling user interactions

After a user reads the message, you need to explicitly set the LPInboxMessage as read.

[message read];
message.read()

You also need to explicitly remove the message from the Inbox if they delete it.

[message remove];
message.remove()

Implementing Inbox in your app

A natural UIView for App Inbox is UITableView because table cells have title, subtitle and image properties.

You can see our example implementation of App Inbox in a TableView in Objective C and in Swift.

Be sure to use onChanged to handle asynchronous changes from Leanplum.

Updating from Newsfeed to Inbox

In previous versions of our iOS SDK (>= 1.3.8 < 1.6.0), App Inbox is called Newsfeed. We renamed the classes (and added a bit more functionality to the new classes) in iOS SDK 1.6.0. Newsfeed is now LPInbox and NewsfeedMessage is now LPInboxMessage.

To update your code, you simply need to instantiate the Inbox with the new class and update your object types.

Since the LPInbox and LPInboxMessage classes have the same methods as the previous Newsfeed and NewsfeedMessage classes, actions like getting messages, getting message counts, marking messages as read, removing messages, etc. can be done as before using the same methods on the new classes.

Custom In-App Messaging Templates

The built-in in-app messaging templates are open-source. You can modify them, delete them, or create your own. The dashboard will reflect your new templates the next time you run your app.

Add the files LPMessageTemplates.h and LPMessageTemplates.m that come with the SDK into your project. Then, initialize them before the [Leanplum start] call:

[LPMessageTemplates sharedTemplates];

Study LPMessageTemplates.m to see how the messages are implemented. To define a new template, use the method [Leanplum defineAction:ofKind:withArguments:withResponder:]. Here's how it works:

  • defineAction: The name of the action or message type you are defining.
  • ofKind: One or more action kinds OR'ed together. kLeanplumActionKindMessage will appear in the Message Type list for creating message campaigns. kLeanplumActionKindAction will appear in the dropdown when choosing an action within a message.
  • withArguments: A list of LPActionArg objects, with each one being an argument that the marketer can fill out on the dashboard. For example, Title, Accept action, or Color.
  • withResponder: A block that accepts an LPActionContext and returns a BOOL whether the action was handled or not. You may decide not to handle the action based on some additional logic defined in the responder. From the context, you can access the values for any argument you defined in the template, as well as some other special methods:
    • [LPActionContext runActionNamed:]: Runs another action when this action is done. The action name should correspond to a named LPActionArg of kind Action.
    • [LPActionContext runTrackedActionNamed:]: Runs another action and tracks an event that the action occurred.
    • [LPActionContext track:withValue:andParameters:]: Tracks an event associated with the current action.
    • [LPActionContext muteFutureMessagesOfSameKind]: Don't show the current message campaign again on this device. For example, if you have a survey popup every 10th session, you may want the ability for the user to decide to remind them later, which simply dismisses the message, or the option to never see this survey again.