I have an issue with Notification Service Extension. I have followed step by step documentation https://developer.xamarin.com/guides/ios/platform_features/introduction-to-ios10/user-notifications/enhanced-user-notifications/#Working_with_Service_Extensions
To implement, I have done in that way.
- Added Notification Service Extension with same prefix of my app (adding a suffix, ex: APP: com.testapp.main - EXT: com.testapp.main.notificationextension)
- Created APPID identifier com.testapp.main.notificationextension into Member Center of Apple
- Created certificate and provisioning profile to send push notification for APP ID com.testapp.main.notificationextension
- Imported into Xcode and Xamarin certificate and provisioning
- Build my app with reference to Notification Extension reference.
- Created archive to upload to TestFlight
- Signed app with its Distribution Certificate and Provisioning Profile
- Signed extension with its Distribution Certificate and Provisioning Profile
- Uploaded to TestFlight
- Download and allowed push notification for my app
- Sent rich push notification with Localytics Dashboard for messaging - Device receive push notification but not pass for NotificationService.cs code of Notification Service Extension!
This is my NotificationService code:
using System;
using Foundation;
using UserNotifications;
namespace NotificationServiceExtension
{
[Register("NotificationService")]
public class NotificationService : UNNotificationServiceExtension
{
Action<UNNotificationContent> ContentHandler { get; set; }
UNMutableNotificationContent BestAttemptContent { get; set; }
const string ATTACHMENT_IMAGE_KEY = "ll_attachment_url";
const string ATTACHMENT_TYPE_KEY = "ll_attachment_type";
const string ATTACHMENT_FILE_NAME = "-localytics-rich-push-attachment.";
protected NotificationService(IntPtr handle) : base(handle)
{
// Note: this .ctor should not contain any initialization logic.
}
public override void DidReceiveNotificationRequest(UNNotificationRequest request, Action<UNNotificationContent> contentHandler)
{
System.Diagnostics.Debug.WriteLine("Notification Service DidReceiveNotificationRequest");
ContentHandler = contentHandler;
BestAttemptContent = (UNMutableNotificationContent)request.Content.MutableCopy();
if (BestAttemptContent != null)
{
string imageURL = null;
string imageType = null;
if (BestAttemptContent.UserInfo.ContainsKey(new NSString(ATTACHMENT_IMAGE_KEY)))
{
imageURL = BestAttemptContent.UserInfo.ValueForKey(new NSString(ATTACHMENT_IMAGE_KEY)).ToString();
}
if (BestAttemptContent.UserInfo.ContainsKey(new NSString(ATTACHMENT_TYPE_KEY)))
{
imageType = BestAttemptContent.UserInfo.ValueForKey(new NSString(ATTACHMENT_TYPE_KEY)).ToString();
}
if (imageURL == null || imageType == null)
{
ContentHandler(BestAttemptContent);
return;
}
var url = NSUrl.FromString(imageURL);
var task = NSUrlSession.SharedSession.CreateDownloadTask(url, (tempFile, response, error) =>
{
if (error != null)
{
ContentHandler(BestAttemptContent);
return;
}
if (tempFile == null)
{
ContentHandler(BestAttemptContent);
return;
}
var cache = NSSearchPath.GetDirectories(NSSearchPathDirectory.CachesDirectory, NSSearchPathDomain.User, true);
var cachesFolder = cache[0];
var guid = NSProcessInfo.ProcessInfo.GloballyUniqueString;
var fileName = guid + ATTACHMENT_FILE_NAME + imageType;
var cacheFile = cachesFolder + fileName;
var attachmentURL = NSUrl.CreateFileUrl(cacheFile, false, null);
NSError err = null;
NSFileManager.DefaultManager.Move(tempFile, attachmentURL, out err);
if (err != null)
{
ContentHandler(BestAttemptContent);
return;
}
UNNotificationAttachmentOptions options = null;
var attachment = UNNotificationAttachment.FromIdentifier("localytics-rich-push-attachment", attachmentURL, options, out err);
if (attachment != null)
{
BestAttemptContent.Attachments = new UNNotificationAttachment[] { attachment };
}
ContentHandler(BestAttemptContent);
return;
});
task.Resume();
}
else {
ContentHandler(BestAttemptContent);
}
}
public override void TimeWillExpire()
{
// 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.
ContentHandler(BestAttemptContent);
return;
}
}
}
You are doing everything correctly, this is an issue raised by a few other xamarin developers. From what I can tell, as soon as you run the NSURLSession to download something, even if it's super super small, you go above the memory limit allowed for this type of extension. This is most probably very specific to xamarin. Here is the link to the bugzilla. https://bugzilla.xamarin.com/show_bug.cgi?id=43985
The work-around/hack I found is far from ideal. I rewrote this app extension in xcode in objective-c (you could use swift too I suppose). It is a fairly small (1 class) extension. Then built it in xcode using the same code signature certificate / provisioning profile and then found the .appex file in the xcode's output.
From then, you can take the "cheap way", and swap this .appex file in your .ipa folder manually just before resigning and submitting the app. If that's good enough for you, you can stop here.
Or you can automate this process, to to so, place the appex file in the csproj's extension and set the build-action as "content". Then in this csproj's file (you'll need to edit directly) you can add something like this. (In this case, the file is called Notifications.appex and is placed in a folder called NativeExtension)
This gives you the general idea, but obviously if you want to support ad-hoc distribution signature, iOS app-store distribution signature you will need to add a bit more code into this (and possibly add in the csproj a native appex file for each different signature), I would suggest putting such xml code in separate ".targets" file and use conditional calltargets in the csproj. Like this: