Adding a service extension to your app

Service extensions are intended to act as middleware for push notifications. A service extension receives a notification before it's displayed to the user. This allows you to manipulate or enrich the notification's content before it's shown to the user.

A service extension is perfect if you're implementing end-to-end encryption, for example. Another great use for a service extension is to download a media attachment from a push notification, save it locally, and add it as a media attachment to the notification contents, because all media attachments that are shown in a notification must be stored locally on the device. This means that a push notification can't really have media attachments, unless a service extension is used to download and store the media locally, before appending it to the notification.

Service extensions can only be implemented for push notifications that have the mutable-content property added to their aps payload, as shown in the following sample:

{  
  "aps": {  
    "alert": “You have a new message!”,  
    "badge": 1,  
    "mutable-content": 1  
  },  
  "custom-encrypted-message": "MyEncryptedMessage"  
}

When the mutable-content property is detected by iOS, your service extension is activated and receives the notification before it's displayed to the user. A service extension is created in the same way as other extensions. You go to the project settings in Xcode, and in the sidebar that shows all targets, you click the + icon. In the dialog that appears, you select Notification Service Extension and give it a name, and Xcode will provide you with the required boilerplate code. When you add a service extension, a sample extension is added to your project to illustrate what a service extension that updates the notification's body text looks like.

Imagine that the Notifications app uses a backend server that sends push notifications when a certain reminder is due. To maintain the user's privacy, you send the following payload to the user:

{  
  "aps": {  
    "alert": “A reminder is due.”,  
    "badge": 1,  
    "mutable-content": 1  
  },  
  "reminder-uuid": "D82ED5AE-C363-46AF-9B0D-301C2E21C58E"  
}

The preceding payload contains a very plain message and has the unique identifier for the reminder added as a custom property. You can implement a service extension to read that identifier, and retrieve the appropriate reminder from the Core Data store to show it to the user.

First, add a new Notification Service Extension to the Notifications app and name it ReminderContent. After doing this, you need to make sure that the extension and the app can both access the Core Data database. Enable the App Groups capability for the app and create a new app group identifier. Make sure to enable App Groups for the extension as well, and add the extension to the group you just created for the app. Next, make sure the .xcdatamodeld and PersistentHelper.swift files are added to both the app and the extension target, and then update the code to load the persistent container in PersistentHelper.swift as follows:

static let persistentContainer: NSPersistentContainer = {
  let container = NSPersistentContainer(name: "Notifications")

  let containerUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.donnywals.notifications-app")!
  let databaseUrl = containerUrl.appendingPathComponent("Notifications.sqlite")
  let description = NSPersistentStoreDescription(url: databaseUrl)
  container.persistentStoreDescriptions = [description]

  container.loadPersistentStores(completionHandler: { (storeDescription, error) in

  })
  return container
}()
}

The process of sharing a Core Data store, as outlined in the preceding section, is described in more detail in Chapter 20, Streamlining Experiences With Siri. Once you have set up the shared Core Data store, let's have a look at the service extension example that Xcode has generated for you.

The boilerplate code that Xcode generates for a  service extension is rather interesting. Two properties are created: contentHandler and bestAttemptContent. The properties are initially given a value in didReceive(_:withContentHandler:). This method is called as soon as the extension is expected to handle the notification.

If the extension fails to call the callback handler in a timely manner, the system calls serviceExtensionTimeWillExpire(). This is essentially the last chance to quickly come up with content for the notification. If the extension still fails to call the callback, the pushed notification is displayed to the user in its original form. The default version of serviceExtensionTimeWillExpire(), generated by Xcode, simply calls the system callback with the notification immediately to ensure a timely response.

Add the following implementation for didReceive(_:withContentHandler:) to retrieve the identifier from the push message payload that was shown previously, and create a new notification with the appropriate reminder. Don't forget to import Core Data at the top of NotificationService.swift:

override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
  self.contentHandler = contentHandler
  bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

  guard let identifier = request.content.userInfo["reminder-uuid"] as? String else {
    contentHandler(request.content)
    return
  }

  let predicate = NSPredicate(format: "identifier == %@", identifier)
  let fetchRequest: NSFetchRequest<Reminder> = Reminder.fetchRequest()
  fetchRequest.predicate = predicate

  let moc = PersistentHelper.persistentContainer.viewContext
  guard let results = try? moc.fetch(fetchRequest),
    let reminder = results.first else {
      contentHandler(request.content)
      return
  }

  if let bestAttemptContent = bestAttemptContent {
    bestAttemptContent.title = "Reminder"
    bestAttemptContent.body = reminder.title ?? ""
    bestAttemptContent.categoryIdentifier = "reminder"

    contentHandler(bestAttemptContent)
  }
}

The preceding code extracts data from the pushed notification to find the appropriate reminder to show to the user, and applies this information to the original notification. This means that the server can send a very candid notification that contains no information about the reminder itself, apart from its identifier. It isn't until the notification reaches the device that the notification is transformed and enriched to show the reminder details. Pretty neat, right?

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.140.5