Developing custom Pega Mobile Client modules for iOS in Pega 7.2.2
Pega Mobile Client consists of several modules that enable various device functionalities or allow your custom mobile app to communicate with the Pega® Platform.
This tutorial describes how iOS developers can extend the Pega Mobile Client by creating their own modules, which can be developed in Objective-C or Swift, and expose the module's functionality through the JavaScript API. Custom modules can enable features and functions that are available to native iOS applications in Pega Platform apps.
For example, a custom module can allow a Pega Platform mobile app to use an embedded laser scanner to scan and recognize barcodes to be pasted into a product ID field, or to use a tablet's projector module to display the wiring layout of equipment that needs to be serviced.
After they are created, custom modules are bundled with the custom mobile app in Designer Studio. For more information, see Uploading custom modules.
This tutorial describes how to build a custom module.
- Technical overview
- Prerequisites
- Creating a custom module project
- Adding third-party dependencies
- Extending the container with plug-in classes
- Extending the container with extension classes
- Testing the custom module
- Enabling the logger API
- Packaging the custom module
Technical overview
Custom modules that extend the Pega Mobile Client are created in Xcode. They facilitate the process of exposing the features and functions that are available to native applications. Custom modules can consist of several parts, such as plug-in classes (the JavaScript code and the native implementation), extension classes, graphic files, sounds, and other required assets.
Product modules
This tutorial uses the PMBase module, which contains the container's basic functionality and is required for creating a custom module. Your custom module can have dependencies to modules other than PMBase. All Pega Mobile Client modules are available in the Frameworks directory of the distribution package.
Plug-in classes
Custom modules contain one or more plug-in classes. These classes use a JavaScript-native side bridge to respond to JavaScript requests and to initiate JavaScript events.
All plug-in classes on iOS must inherit from the PMJavaScriptPlugin
class. Classes that inherit from this class are instantiated for each WKWebView instance when it is created, and remain valid within this instance for its lifetime.
A plug-in is needed to return a JavaScript code as an NSString
class. The plug-in is injected into WKWebView JavaScript code that can be hardcoded, generated, or read from the resource bundle. Pega Mobile Client asks for the JavaScript code by calling the provideJavaScriptWithCompletionHandler:
method on the PMJavaScriptPlugin instance.
JavaScript code returned by the plug-in is injected into the WKWebView instance during the web application startup before the onLaunchboxLoaded
method is called. An instance of the bridge object is available during the injection phase. JavaScript code must use this instance to communicate with the native code.
To learn how to create plug-in classes, refer to Extending the container with plug-in classes.
Extension classes
Use extension classes to extend the container, for example, to grant access to native events. Custom modules contain one or more extension classes. An extension class enables simple initialization, such as creating single instances or registering for UIApplication
notifications. Its instance is created once, at container startup, and remains valid within the container for its lifetime. If the custom module library contains more than one extension class, Pega Mobile Client creates an instance for each of them.
All extension classes on iOS must inherit from the PMApplicationPlugin
class. Such classes are instantiated when the application is initiated, and remain valid for its lifetime.
To learn how to create extension classes, refer to Extending the container with extension classes.
Sample custom module
This article includes a working example of a custom module for you to analyze. Download the following file to import it in Xcode:
Known issues
Because of compatibility issues, you cannot use the networkActivityIndicatorVisible
property of the UIApplication
method to control the network activity indicator. Use the PMNetworkActivityIndicator
class instead, which has been exposed in the Pega Mobile Client's native API. Refer to the documentation provided in the distribution package.
Prerequisites
Before developing a custom module for iOS:
- Obtain the distribution package for your intended platform from a Pegasystems Global Customer Support representative.
- Download and configure the following items:
- Mac OS X 10.11.5 or later (the latest update is recommended)
- Ruby 2.0.0 or later (already installed with OS X)
- Bundler gem 1.13.6 or later
The bundler package must be manually installed, for example, by callingsudo gem install bundler
in a terminal window. - -->
- Xcode 8.2.1
- Java Runtime Environment (JRE) 8 x64
- Mobile provision profile prepared for the app
For more information, see Creating Development Provisioning Profiles on the Apple Developer Portal. - A valid development and distribution certificate
For more information, see the Certificate, Key, and Trust Services Programmer's Guide on the Apple Developer Portal.
- Review the documentation in the doc folder of the distribution package to become familiar with the native custom module API and JavaScript API.
Creating a custom module project
Prepare the Xcode project for your custom module by doing the following steps:
Run Xcode, click New project, and select Cocoa Touch Framework.
Click Product name field, enter a custom module name, for example, MyModule, and select your preferred programming language.
. In theClick
and save your project in the Modules folder of your distribution package.Browse to the Frameworks folder of the distribution package. Drag the PMBase.framework module to your project in Xcode.
Accept the default settings in Xcode, and click
.Navigate to the Build Settings tab in your project and set the Framework search paths parameter to $(PROJECT_DIR)/../../Frameworks.
Adding third-party dependencies
You can extend the procedure by integrating third-party libraries written in Objective-C or Swift with your custom module. To add a library project, a statically linked library, or a dynamically linked library to your custom module, do the following steps:
To add a library project that includes source code:
- Drag the library project into Xcode to create a subproject of your custom module project.
- In the Project Navigator pane, select your custom module project to access its settings.
- Select your target, and in the Build Phases tab:
- Add the third-party library target in the Target Dependency section.
- Add the third-party library in the Link Binary With Libraries section.
- Optional: If your third-party library requires any system libraries, add them in the Link Binary With Libraries section.
To add a statically linked library that contains Objective-C code:
- Drag the library project into Xcode. Select Copy items if needed to add all the files to your project directory.
- In the Project Navigator pane, select your custom module project to access its settings.
- Select your target, and:
- In the Build Phases tab, link the *.a library file against your targets in the Link Binary With Libraries section.
- In the Build Settings tab:
- Reference the directory in the Library Search Paths section.
- Add the $(PROJECT_DIR) path in the User Header Search Paths section with Recursive selected.
- Add the -ObjC flag in the Other Linker Flags section.
- Optional: If your third-party library requires any system libraries, add them in the Link Binary With Libraries section of the Build Phases tab.
To add a dynamically linked framework that contains the Objective-C or Swift code:
- Drag the framework into Xcode. Click Copy items if needed to add it to your project directory.
- In the Project Navigator pane, select your custom module project to access its settings.
- Select your target, and:
- In the Build Phases tab, link the framework against your targets in the Link Binary With Libraries section.
- In the Build Settings tab, add the $(PROJECT_DIR) path in the Framework Search Paths section.
- In your target's Build Phases tab, add a new Copy Files phase:
- In the Destination field, select Products Directory.
- Add the third-party framework in the area below.
Extending the container with plug-in classes
All JavaScript communication must pass through the main GCD queue (UI classes can be called only on the main thread). To conserve performance of the UI and JavaScript bridge, all JavaScript code that does not explicitly require using the main queue must be run on private GCD queues. This is why the dispatchQueue
method has been implemented in the PMJavaScriptPlugin
class used by the JavaScript bridge on iOS to ask plug-in instances for their queue when passing JavaScript calls.
To create the main plug-in class:
In the Project Navigator pane, go to the MyModule folder.
Click File > New File.
Select Cocoa Touch Class.
On the next screen, in the Class field, enter the name of the class. In the Subclass field, enter PMJavaScriptPlugin.
In the Language field, select Objective-C or Swift.
Click
, and then click .In the newly created class, import the PMBase module by using the
@import PMBase;
declaration.If you selected Objective-C in the Language field, place the import statement in the header file. Otherwise, place it at the top of the Swift file.
To implement the JavaScript side of the plug-in, do the following steps:
In the Project Navigator pane, go to the Resources folder.
Click File > New File to create a JavaScript file, for example, proximitySensor.js.
Click Create.
, and then clickEdit the resource to create code similar to the following example:
(function() { window.example = window.example || {}; window.example.ProximitySensor = { // The function makes a bridge call to native code and passes an object with a single function reference. // The callbacks is temporary and will be invalidated after first response is received. isCloseToTheUser: function (callback) { bridge.call('ProximitySensorExample', 'isCloseToTheUser', null, { 'onSuccess': callback }); } }; })();
The name of the plug-in class used in thebridge.call
method must be the same as the one that was defined when the class was created.
To return the JS code, implement the provideJavaScriptWithCompletionHandler:
method (for Objective-C) or provideJavaScript(completion:)
(for Swift) in your JavaScript code. Refer to the following example of the Objective-C implementation.
The header file should contain code similar to the following example:
@import PMBase; @import UIKit; NS_ASSUME_NONNULL_BEGIN @interface ProximitySensorJSPlugin : PMJavaScriptPlugin #pragma mark - Called from JavaScript - (void) isCloseToTheUser:(nullable id)data withCallback:(nullable PMJavaScriptCallback *)callback; @end
- The implementation file should contain code similar to the following example:
#import "ProximitySensorJSPlugin.h" @interface ProximitySensorJSPlugin () @end @implementation ProximitySensorJSPlugin + (NSString *)name { return @"ProximitySensorExample"; } - (instancetype)initWithContext:(PMJavaScriptPluginContext *)context { if (self = [super initWithContext:context]) { // Do initialization here... } return self; } - (void)provideJavaScriptWithCompletionHandler:(void (^)(NSString *_Nonnull))completionHandler { NSString *scriptPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"proximitySensor" ofType:@"js"] NSString *scriptCode = [NSString stringWithContentsOfFile: encoding:NSUTF8StringEncoding error:NULL]; completionHandler(scriptCode); } #pragma mark - Called from JavaScript - (void)isCloseToTheUser:(nullable id)data withCallback:(nullable PMJavaScriptCallback *)callback { dispatch_async(dispatch_get_main_queue(), ^{ BOOL proximityState = [[UIDevice currentDevice] proximityState]; [callback call:"onSuccess" passingData:@(proximityState)]; }); }
To add any other resources, including configuration files, audio files, graphics, and videos, drag them to your project in Xcode, select Copy items if needed, and click .
Extending the container with extension classes
To create the main extension class, you must create a subclass of the PMApplicationPlugin
class. Pega Mobile Client instantiates all subclasses of the PMApplicationPlugin
class by calling the init
selector in the Pega Mobile Client's UIApplicationDelegate
init file. The extension class can react to the events from the UIApplicationDelegate
protocol that are defined in the PMUIApplicationDelegate
protocol.
To create the extension class:
In the Project Navigator pane, go to the MyModule folder.
Click File > New File.
Select Cocoa Touch Class.
On the next screen, enter the name of the class in the Class field. In the Subclass field, enter PMApplicationPlugin.
Select Objective-C or Swift in the Language field.
Click Create.
, and then clickIn the newly created class, import the PMBase module by using the
@import PMBase;
declaration.If you selected Objective-C in the Language field, place the import statement in the header file. Otherwise, place it at the top of the Swift file.Import and implement the
PMApplicationPlugin
protocol. Refer to the following example of the Objective-C implementation.The header file should contain code similar to the following example:
@import PMBase; @import UIKit; NS_ASSUME_NONNULL_BEGIN @interface ProximitySensorJSPlugin : PMApplicationPlugin @end NS_ASSUME_NONNULL_END
- The implementation file should contain code similar to the following example:
#import "ProximitySensorExtension.h" @implementation ProximitySensorExtension - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [super application:application didFinishLaunchingWithOptions:launchOptions]; [[UIDevice currentDevice] setProximityMonitoringEnabled:YES]; return YES; } - (void)applicationDidEnterBackground:(UIApplication *)application { [super applicationDidEnterBackground:application]; [[UIDevice currentDevice] setProximityMonitoringEnabled:NO]; } - (void)applicationWillEnterForeground:(UIApplication *)application { [super applicationWillEnterForeground:application]; [[UIDevice currentDevice] setProximityMonitoringEnabled:YES]; } @end
Testing the custom module
To test your custom module locally:
- Modify the app.properties file that is in the distribution package's main directory:
Change a value of the
bootstrap.config.dir
parameter toresources/pega7OfflineBootstrap
.Set a value of the
bootstrap.string.applicationURL
to your intended instance of the Pega Platform, for example,http://test.server.com:8243/prweb/
.Set the other parameters as necessary (review the inline comments for details).
Run the
ant
command at a command prompt to build a binary Pega Mobile Client package that includes your custom module.Open the HybridClient.xcworkspace project from the distribution package in Xcode.
Drag the custom module project into the HybridClient.xcworkspace as a subproject of the HybridClient project.
Open the Modules.xcconfig file and add
-framework MyModule
at the end of the line.Click
. The Pega Mobile Client starts on a connected device.
Enabling the logger API
Compared with an iOS original NSLog macro, the Logger API is more reliable. It allows:
- Access to its logs
- Setting a logging level
- Speeding up log recording tenfold
To use the Logger API in the custom module's native code:
Import the PMBase module into your class.
Use the following macros in your code:
PMLogError()
,PMLogWarn()
,PMLogInfo()
,PMLogDebug()
andPMLogVerbose()
, which correspond to logging levels that are described in the Logger API reference.
Packaging the custom module
When your custom module is complete and has been tested, prepare it for packaging within a .zip file. To obtain a sample package, download the following file:
custom-module-assets.zip (0.83 MB)
Set the Active Scheme in Xcode to Generic iOS Device.
Click > to compile your custom module.
Find the custom module binary by right-clicking the framework in the Products directory of the Project Navigator pane and clicking . The file name will be MyModule.framework.
If you are using the sample custom-module-assets.zip file, remove the default contents of the modules-ios folder.
Copy the module binary to the modules-ios folder in the custom-module-assets.zip file.
Upload the custom-module-assets.zip file with your custom module to the Pega Platform. For more information, see Uploading custom modules.