Adding images to push notifications

Push notification images and requirements

Apart from text, push notifications can include an image or gif, enriching the customer experience. Before you start sending Rich Push Notifications, there are a few steps that must be followed:

Configuring your app for rich push

Before you can add images to your push notifications, you should check with your development team to ensure your app has been properly configured to receive rich push. See our full push setup instructions and rich push setup for more.

The image will only be visible if your app is configured to support rich push, and only on devices with the minimum required OS version. Otherwise, users will see just the message text.

iOS limitations

  • Device must be on iOS 10+
  • Leanplum SDK 1.4.2
  • Images in iOS 12 Rich Push Notifications will only display in two ratios, 1:1 and 3:2. If the width:height ratio of an image doesn't fit these guidelines, the image will be trimmed from the center to match one of the accepted ratios (whichever preserves the most pixels)
  • Complete the setup steps listed below.

Android limitations

  • Device must be on Android 4.1+ (API Level 16)
  • Leanplum SDK 1.3.2
  • Android images require an aspect ratio of 2:1 (consider trying sizes 512x256, 960x480, or 1024x512). Note: iOS does not define a required aspect ratio. Depending on the screen size and ppi density of different devices, cropping may occur past this ratio. Therefore, we recommend setting a safe margin of 30px around the edges of the image where no valuable content is stored.
  • We use the BigPicture mode for Android notifications. In this format, only one line of summary is shown when the message is expanded, regardless of the image size. This is enforced by Android and without having a custom layout for the push notification it is not possible to have a multiline summary with a big picture. The summary is intended to complement the image and should be very short while the main focus should be on the image.
  • With GIFs, Android will only display a static image in the notification.

Adding an image to a push notification

We recommend thoroughly testing on both iOS and Android devices to ensure the image is rendering as expected (see Android limitations above).

Make sure your message copy still makes sense without the image so that users on incompatible app versions will still understand the message. If you would like to send your message only to users who are rich push-compatible, reach out to your customer success manager and they can add a custom target for you.

  1. Create a new push notification campaign.
  2. Choose targets.
  3. Click the Image field to choose an image.
  4. Upload an image. If possible, we recommend uploading images directly to Leanplum. Alternatively, you can provide your own image URL. JPEG, PNG, and GIF files up to 1MBs are supported. URLs must use valid HTTPS. If providing an image URL, iOS requires URLs to support TLS 1.2.
  5. Preview the push to verify the image displays on device as expected.
  6. Send the message.

iOS setup

1. Create a Notification Service Extension

Within your project, you must create a Service Extension. A Service Extension allows for the modification of a notification to include rich media. To add a notification service extension, click on File > New > Target and select Notification Service Extension.

1464

2. Setting up Notification Service Extension

Since the Notification Service Extension has its own bundle id, it must be set up with its own App ID and provisioning profile. Please verify through the Apple Developer Service.

3. Add the following code to Notification Service Extension

While you are free to implement your own method within the Notification Service Extension, you will need to make sure that your code is correctly handling the media that is being sent with Leanplum. The key that is associated with the rich media in the Leanplum Payload is: LP_URL.

class NotificationService: UNNotificationServiceExtension {

    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        let imageKey = "LP_URL"
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
        
        // MARK: - Leanplum Rich Push
        if let bestAttemptContent = bestAttemptContent {
            let userInfo = request.content.userInfo;
            
            // LP_URL is the key that is used from Leanplum to
            // send the image URL in the payload.
            //
            // If there is no LP_URL in the payload than
            // the code will still show the push notification.
            if userInfo[imageKey] == nil {
                contentHandler(bestAttemptContent);
                return;
            }
            
            // If there is an image in the payload,
            // download and display the image.
            if let attachmentMedia = userInfo[imageKey] as? String {
                let mediaUrl = URL(string: attachmentMedia)
                let LPSession = URLSession(configuration: .default)
                LPSession.downloadTask(with: mediaUrl!, completionHandler: { temporaryLocation, response, error in
                    if let err = error {
                        print("Leanplum: Error with downloading rich push: \(String(describing: err.localizedDescription))")
                        contentHandler(bestAttemptContent);
                        return;
                    }
                    
                    let fileType = self.determineType(fileType: (response?.mimeType)!)
                    let fileName = temporaryLocation?.lastPathComponent.appending(fileType)
                    
                    let temporaryDirectory = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName!)
                    
                    do {
                        try FileManager.default.moveItem(at: temporaryLocation!, to: temporaryDirectory)
                        let attachment = try UNNotificationAttachment(identifier: "", url: temporaryDirectory, options: nil)
                        
                        bestAttemptContent.attachments = [attachment];
                        contentHandler(bestAttemptContent);
                        // The file should be removed automatically from temp
                        // Delete it manually if it is not
                        if FileManager.default.fileExists(atPath: temporaryDirectory.path) {
                            try FileManager.default.removeItem(at: temporaryDirectory)
                        }
                    } catch {
                        print("Leanplum: Error with the rich push attachment: \(error)")
                        contentHandler(bestAttemptContent);
                        return;
                    }
                }).resume()
                
            }
        }
    }
    
    override func serviceExtensionTimeWillExpire() {
        // Called just before the extension will be terminated by the system.
        // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
        if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }
    
    // MARK: - Leanplum Rich Push
    func determineType(fileType: String) -> String {
        // Determines the file type of the attachment to append to URL.
        if fileType == "image/jpeg" {
            return ".jpg";
        }
        if fileType == "image/gif" {
            return ".gif";
        }
        if fileType == "image/png" {
            return ".png";
        } else {
            return ".tmp";
        }
    }

}
@interface NotificationService ()

@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;

@end

@implementation NotificationService

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    NSDictionary *userInfo = request.content.userInfo;
    
    // LP_URL is the key that is used from Leanplum to
    // send the image URL in the payload.
    //
    // If there is no LP_URL in the payload than
    // the code will still show the push notification.
    if (userInfo == nil || userInfo[@"LP_URL"] == nil) {
        self.contentHandler(self.bestAttemptContent);
        return;
    }
    
    NSString *attachmentMedia = userInfo[@"LP_URL"];
    
    // If there is an image in the payload, this part
    // will handle the downloading and displaying of the image.
    if (attachmentMedia) {
        NSURL *URL = [NSURL URLWithString:attachmentMedia];
        NSURLSession *LPSession = [NSURLSession sessionWithConfiguration:
                                   [NSURLSessionConfiguration defaultSessionConfiguration]];
        [[LPSession downloadTaskWithURL:URL completionHandler: ^(NSURL *temporaryLocation, NSURLResponse *response, NSError *error) {
            if (error) {
                NSLog(@"Leanplum: Error with downloading rich push: %@",
                      [error localizedDescription]);
                self.contentHandler(self.bestAttemptContent);
                return;
            }
            
            NSString *fileType = [self determineType: [response MIMEType]];
            NSString *fileName = [[temporaryLocation.path lastPathComponent] stringByAppendingString:fileType];
            NSString *temporaryDirectory = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
            [[NSFileManager defaultManager] moveItemAtPath:temporaryLocation.path toPath:temporaryDirectory error:&error];
            
            NSError *attachmentError = nil;
            UNNotificationAttachment *attachment =
            [UNNotificationAttachment attachmentWithIdentifier:@""
                                                           URL:[NSURL fileURLWithPath:temporaryDirectory]
                                                       options:nil
                                                         error:&attachmentError];
            if (attachmentError != NULL) {
                NSLog(@"Leanplum: Error with the rich push attachment: %@",
                      [attachmentError localizedDescription]);
                self.contentHandler(self.bestAttemptContent);
                return;
            }
            self.bestAttemptContent.attachments = @[attachment];
            self.contentHandler(self.bestAttemptContent);
            [[NSFileManager defaultManager] removeItemAtPath:temporaryDirectory error:&error];
        }] resume];
    }
    
}
    
- (NSString*)determineType:(NSString *) fileType {
    // Determines the file type of the attachment to append to NSURL.
    if ([fileType isEqualToString:@"image/jpeg"]){
        return @".jpg";
    }
    if ([fileType isEqualToString:@"image/gif"]) {
        return @".gif";
    }
    if ([fileType isEqualToString:@"image/png"]) {
        return @".png";
    } else {
        return @".tmp";
    }
}
    
- (void)serviceExtensionTimeWillExpire {
    // Called just before the extension will be terminated by the system.
    // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
    self.contentHandler(self.bestAttemptContent);
}

@end

4. Update didReceiveRemoteNotification

Please make sure to update the application instance method didReceiveRemoteNotification so that it does execute the download of the rich media. See below for a simple example.

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

5. Create a Rich Push from our Dashboard

After implementing the code above, you are free to create a rich push notification from our dashboard. Make sure to try it out on a registered test device before sending to users.

Set a dynamic image for Rich push messages

You can use Jinja syntax to set a rich push image that changes based on a parameter value for a triggered push notification.

  1. Create a new Push notification message.

  2. Set the URL of the image using new syntax Jinja to include your parameter.

https://myimages.com/destinations/cities/{{parameter['dest_code']}}.jpg
682

Make sure you use brackets around the parameter name (see 'dest_code' above).

  1. Trigger the push notification off the event whose parameter matches your image URL.
1548

Android event with parameter example:

HashMap<String, Object> paramsFlight = new HashMap<String, Object>();
paramsFlight.put("dest_code", "Varna");
Leanplum.track("flight_search", paramsFlight);

iOS event with parameter example:

Leanplum.track("flight_search", withParameters: ["dest_code": "Varna"])
  1. Test your new push notification on a real device before sending to users!

See iOS and Android examples below:

433 904

Things to note:

  • If the image does not exist, the push will be sent without the image. The image is resolved on the app, so the push will still be sent, but the image URL will not return an image.
  • If the parameter ("dest_code" in this example) is not provided in the event, the push will not be sent. Failing to provide the right parameter with the event will cause the Jinja customization to fail, which causes the entire message to not send.

Android Notification Badge (Dots)

In the following section, we will show you how to enable Android Notification Badges(Notification Dots) for your Android application. With this option, a dot on the app icon will be shown, when there is a pending push notification. It can show images as well.

📘

The badges feature is supported from Leanplum Android SDK 3.0.0 and above. It is available for devices on Android O (API 26) and above and the Dots could vary in different launchers.

For more information, refer to the below-attached articles from Android:

  1. Icon-badge
  2. Badges

Step-by-step guide:

  1. Create a new channel while using the addAndroidNotificationChannel API method and set the showBadge parameter.

Example:

{
  "appId": "APP_ID",
  "clientKey": "DEV_KEY",
  "apiVersion": "1.0.6",
  "id": "badges_vibr_magenta",
  "name": "Badge, vibration and magenta",
  "importance": 3,
  "enableLights": true,
  "enableVibration": true,
  "default": true,
  "showBadge": true, // Enabled Badge
  "light_color": -65281  // Magenta
}

Things to note:

The notification will appear in the drawer (default behavior), as well as in the notification dots. For rich pushes including an image, the image will not be shown, only the icon (app icon). The image will still be available in the notification drawer.

64 329

If you can not see the Notification Badge, verify if the Notification Dots are enabled as well since you can disable them per app or globally:

1080 1080