klaviyo / klaviyo-swift-sdk

Swift SDK for integrating Klaviyo’s push notifications, event tracking, and user profile management into iOS applications.
https://www.klaviyo.com/mobile-push-marketing
MIT License
12 stars 11 forks source link

Adding rich push notifications to Klaviyo SDK #92

Closed ajaysubra closed 1 year ago

ajaysubra commented 1 year ago

Description

This PR is to add a new target that is exported independently using SPM and Cocoapods to users and will be integrated into the notification service extension that users will create on their mobile apps to handle rich push notifications.

Some considerations that we considered are -

  1. We decided against including the logic to download and attach image into the core SDK mainly because
    • The notification service extensions that users create on their apps are granted limited memory by app and hence bundling our SDK into the extension will unnecessarily bloat up the extension size.
    • There are some application methods that are used in our SDK that are not available to access from an extension such as UIApplication.shared which is used to open deep links.
  2. We decided to move the logic to download image and attach it into the SDK vs. leaving it to our users for the below reasons -
    • Lesser code for our users to maintain
    • Easily to debug user issues when reported.

How would our users include this in their SDK -

SPM -

Since the package is already pulled into user's apps that use Klaviyo, they only need to include this in their NotificationServiceExtension frameworks and targets section of this target -

image

Cocoapods -

Unfortunately, Cocoapods doesn't support sub specs for extensions - GH issue. Because of this we export the extension logic as a separate pod which user can import in their notification service extension target as below -

target 'NotificationServiceExtension' do
    pod 'KlaviyoSwiftExtension'
end

^ is separate from the KlaviyoSwift that the main app target imports

The full pod file would look something like -

platform :ios, '13.0'
use_frameworks!

target 'CocoapodsExample' do
  pod 'KlaviyoSwift'
end

target 'NotificationServiceExtension' do
    pod 'KlaviyoSwiftExtension'
end

Once included in the SDK below is how it would be imported and used in the notification service extension -

import UIKit
import UserNotifications
import KlaviyoSwiftExtension

// MARK: notification service extension implementation.

/// When push payload is marked as there being mutable-content this service
/// (more specifically the `didReceiveNotificationRequest` ) is called to perform
/// tasks such as downloading images and attaching it to the notification before it's displayed to the user.
///
/// There is a limited time before which `didReceiveNotificationRequest`  needs to wrap up it's operations
/// else the notification is displayed as received.
///
/// Any property from `UNMutableNotificationContent` can be mutated here before presenting the notification.
class NotificationService: UNNotificationServiceExtension {
    var request: UNNotificationRequest!
    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(
        _ request: UNNotificationRequest,
        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {

        self.request = request
        self.contentHandler = contentHandler
        self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

        if let bestAttemptContent = bestAttemptContent {
            KlaviyoExtensionSDK().handleNotificationServiceDidReceivedRequest(
                request: self.request,
                bestAttemptContent: bestAttemptContent,
                contentHandler: contentHandler
            )
        }
    }

    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 {
            KlaviyoExtensionSDK().handleNotificationServiceExtensionTimeWillExpireRequest(
                request: self.request,
                bestAttemptContent: bestAttemptContent,
                contentHandler: contentHandler
            )
        }
    }
}