Few cents about my commits

Tutorial: using app-extensions in RoboVM iOS application

|

Apple added support for app-extensions while ago (ios8). I’ve created today a PR255 that add ability to include pre-build app extension into RoboVM app. Answering the possible question:

Can I use RoboVM to develop appext

Short answer: no, not today
Long answer: Any RoboVM binary (app, app-extensions, framework etc) will include RoboVM runtime and everything that comes together. This means that both app and extension will be quite big. More over it is not clear enough if it is possible to run two instances of VM. In theory to do this app has to be organized as Embedded framework. Application and app-extensions shall be native wrapper that use this framework. Not good idea.

Building native app-extension for OneSignal robopod

Official documentation for OneSignal integration is available by link. It will require OneSignal.framework itself, easiest way to get it is to build using Carthage:

echo 'github "OneSignal/OneSignal-iOS-SDK"' >> Cartfile
carthage update

First issue: there is no project type **** Service Extension in XCode

Service extension can be added to existing project only as target. So lets create new “Single View App” project in xcode. Fill the options dialog with values as shown on screenshot below:

Add new Notification Service Extension target (File-New target) to newly created project. Enter the product name as OneSignalNotificationServiceExtension and press Finish. Do not activate “OneSignalNotificationServiceExtension” scheme. At this moment project should have following structure:

There is no need in “Single View App” so it can be freely removed:

  • select OneSignal target and hit ‘-‘
  • select OneSignal folder in project tree and hit ‘delete’. Select move to trash.

Adding OneSignal.framework

  • Copy OneSignal.framework to project directory next to OneSignalNotificationServiceExtension folder
  • use finder and drag-and-drop OneSignal.framework to project tree;
  • check that Build Phases - Link Binary With Libraries contain OneSignal.framework

Adding OneSignal code

Just following manual and turning NotificationService.m into following:

#import <OneSignal/OneSignal.h>
#import "NotificationService.h"

@interface NotificationService ()
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent * contentToDeliver);
@property (nonatomic, strong) UNNotificationRequest * receivedRequest;
@property (nonatomic, strong) UNMutableNotificationContent * bestAttemptContent;
@end

@implementation NotificationService
- (void)didReceiveNotificationRequest:(UNNotificationRequest * )request withContentHandler:(void (^)(UNNotificationContent * ) ) contentHandler {
    self.receivedRequest = request;
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];

    [OneSignal didReceiveNotificationExtensionRequest:self.receivedRequest withMutableNotificationContent:self.bestAttemptContent];

    // DEBUGGING: Uncomment the 2 lines below and comment out the one above to ensure this extension is excuting
    //            Note, this extension only runs when mutable-content is set
    //            Setting an attachment or action buttons automatically adds this
    // NSLog(@"Running NotificationServiceExtension");
    // self.bestAttemptContent.body = [@"[Modified] " stringByAppendingString:self.bestAttemptContent.body];

    self.contentHandler(self.bestAttemptContent);
}

- (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.

    [OneSignal serviceExtensionTimeWillExpireRequest:self.receivedRequest withMutableNotificationContent:self.bestAttemptContent];

    self.contentHandler(self.bestAttemptContent);
}
@end

At this moment build should work (Product/Build)

Adding signature

Configure target’s settings General-signing with any certificate and provisioning profile that matches extension bundle id (wildcard provisioning profile rules here) or just create and setup dummy certificate/profile for signing.
In general signature doesn’t matter here as when included to RoboVM project app extension will be resigned. It is only required to build binary with xcodebuild.
UPDATE as at moment of building with Xcode provisioning profile doesn’t matter but AppExtension DOES require own one when emebeded into application. Check this post

Building universal binary

To create fat binary simulator and arm slices are being build separately and then combined using lipo. It is being done by invoking following commands in ternimal. Just make sure to do it in folder whene .xcodeproj is located:

xcodebuild -project onesignal.xcodeproj -target OneSignalNotificationServiceExtension -configuration release -sdk iphoneos -arch arm64 -arch armv7 -arch armv7s BUILD_DIR=build BUILD_ROOT=build
xcodebuild -project onesignal.xcodeproj -target OneSignalNotificationServiceExtension -configuration release -sdk iphonesimulator -arch i386 -arch x86_64 BUILD_DIR=build BUILD_ROOT=build

mkdir "OneSignalNotificationServiceExtension.appex"
lipo -create -output "OneSignalNotificationServiceExtension.appex/OneSignalNotificationServiceExtension" \
    "build/release-iphoneos/OneSignalNotificationServiceExtension.appex/OneSignalNotificationServiceExtension" \
    "build/release-iphonesimulator/OneSignalNotificationServiceExtension.appex/OneSignalNotificationServiceExtension"

cp "build/release-iphoneos/OneSignalNotificationServiceExtension.appex/Info.plist" "OneSignalNotificationServiceExtension.appex/"

# resign with empty signature
codesign -f -s - "OneSignalNotificationServiceExtension.appex"

Application extension is ready to be used with RoboVM

Adding to RoboVM project

PR255 have added following configuration key to robovm.xml:

  • <appExtensionPaths>, type <path>: specifies a list of directories where to look for app ext. Works similar to frameworkPaths;
  • <appExtensions>, type <extension>: list extensions to include in app.

Example:

<appExtensionPaths>
    <path>libs</path>
</appExtensionPaths>
<appExtensions>
    <extension>OneSignalNotificationServiceExtension</extension>
</appExtensions>

UPDATE: Provision profile for application extension

Check this post for details While it is not required to run on device (as per today) but it is required for Apple Store submit. Requirement for profile is following:

  1. It shall contain same signing identity as profile of main application;
  2. It’s application id shall match id of application extension. RoboVM makes bundle id for it as MainAppId + "." + ExtensionName. For OneSignalNotificationServiceExtension it results in com.sample.application.OneSignalNotificationServiceExtension, here com.sample.application is bundle id of applications, so bundle id of provision profile shall be:
    • either exact match to bundle Id RoboVM generates for app extension (e.g. com.sample.application.OneSignalNotificationServiceExtension)
    • wildcard id (e.g. * ) which is preferable way as it allows to have one profile for many extensions.

RoboVM will automatically search for profile that matches the signature and extension bundle id. Also there is an option to explicitly specify it in robovm.xml with profile parameter to extension entry:

 <appExtensions>
     <extension profile="3AED05A9-5F1F-4120-9276-11980B9C88EE">OneSignalNotificationServiceExtension</extension>
 </appExtensions>

Value of profile is same iosProvisioningProfile used with gradle and can have following values:

  • either udid of profile;
  • either profile name;
  • either appIdPrefix;
  • either appIdName.

What RoboVM does to application extensions

  1. During doInstall phase it copies appex to PlugIns folder and updates CFBundleIdentifier in Info.plist as it has extends application ones;
  2. During prepareInstall phase it resigns appex with signature used for application

Complete sample available at github

Comments