Integration

Kumulos provides an SDK in the form of framework to ease integration Kumulos Analytics, Push Notification, Crash Reporting and Build features into your iOS app. This guide provides an overview of setting up the SDK for your project and sample usage.

Integrate SDK components

The Kumulos SDK is an open source project hosted on Github, for ease of use it is linked to from the agency console and can be found at https://github.com/Kumulos/KumulosSdkObjectiveC.

Both Carthage and CocoaPods integration instructions are available in the GitHub repo.

Initialization

To initialize the Kumulos SDK, simply alloc & init the Kumulos class with your application's API key and secret key.

@import KumulosSDK;
...
KSConfig *config = [KSConfig configWithAPIKey:@"YOUR_API_KEY" andSecretKey:@"YOUR_SECRET_KEY"];
[Kumulos initializeWithConfig:config];

A good place to do this is in your AppDelegate application:didFinishLaunchingWithOptions: method.

Checking installs of your App

When you run your app on a simulator or install your app on a device, you can check that the SDK has been initialized correctly by selecting the app and clicking the Installs tab to see the ten most recent installs of your app. Click on any install to see more information.

Recent installs

If you experience any difficulties integrating an SDK or initializing the Kumulos client, please don't hesitate to contact support who are standing by to help!

Installation ID

When initialized for the first time, the Kumulos SDK will create a unique identifier for the app installation that initialized the SDK.

This identifier can be used to target push notifications to a specific device through KScript or the Push Notifications API.

In order to retrieve this installation ID, simply use the class method:

NSString* installId = [Kumulos installId];

Once you have the installation ID, you can send it to your app's backend to be used later for push targeting. For more information about push targeting, please see the KScript Documentation or push notification documentation as appropriate.

Analytics

Session Analytics

Kumulos records sessions based on application foreground and background events. When an app enters the background, Kumulos will wait for a configurable idle period. If there are no more foreground events during that idle period, the current session will be closed.

Session analytics

You can configure the idle period to suit your app's use-case. By default the idle period is 40 seconds of inactivity. The idle period does not count towards session duration calculations.

To adjust the idle period, you can configure the SDK as follows:

KSConfig *config = [KSConfig configWithAPIKey:@"YOUR_API_KEY" andSecretKey:@"YOUR_SECRET_KEY"];
[config setSessionIdleTimeout:120];
[Kumulos initializeWithConfig:config];

Note that due to background task limits, in practise the idle timeout can only reasonably be up to a few minutes

Event Tracking

Kumulos allows you to track custom analytics events that can be used as a trigger to fire automation rules (e.g. to trigger a push notification) or as steps in a funnel to track conversion through key user journeys in your app.

Track conversion

To track a custom analytics event, use trackEvent as follows:

[Kumulos.shared trackEvent:@"product.purchased" withProperties:@{
    @"productId": 42
}];

Event tracking is available offline as all events are persisted locally before being synced to the server in batches in the background.

Checking events from your App

When you run your app on a simulator or install your app on a device, you can check that the SDK is recording events correctly by selecting the app and clicking the Installs tab to see the ten most recent installs of your app. Click on any install and then click on the Events tab.

Install events

Alternatively, to see counts of all analytics events, including system events such as opened a push notification, recorded by installs of the app in the last 30 days, expand 'Analytics' and click 'Explore' in the left menu. For comparison, the total number of sessions in the same period is shown.

Analytics Explorer

User Association

Kumulos allows associating a user identifier with the current installation ID. This user identifier is useful for performing analytics aggregations & analyses at the user level, for example, funnels completed by users across multiple devices. You can also optionally associate a collection of attributes with the user.

To associate the current app installation with a user, you can use the helper method as shown below:

[Kumulos.shared associateUserWithInstall:@"Bob"];

User association is time-aware, so in the case of two different users sharing a device to log in to the app, their user IDs will be associated with the same install for the time between subsequent calls to the helper method. For example:

[Kumulos.shared associateUserWithInstall:@"Bob"];

// This event will belong to Bob
[Kumulos.shared trackEvent:@"product.purchased" withProperties:@{
    @"productId": 42
}];

[Kumulos.shared associateUserWithInstall:@"Alice"];

// This event will belong to Alice
[Kumulos.shared trackEvent:@"product.purchased" withProperties:@{
    @"productId": 42
}];

Attributes

You can optionally associate a collection of attributes with the user for targeting, personalization or attribution. To associate attributes with the user, pass a collection into the helper method as shown.

[Kumulos.shared associateUserWithInstall:@"Bob" attributes:@{
    @"name": "Shawn",
    @"age": 25
}];

If you do not have a user identifier, use the installation id generated by the Kumulos SDK.

Location Tracking

You can send Kumulos location updates and use this to trigger events such as push notifications when an install enters a GeoFence.

How you configure the Core Location services for iOS depends on the specific use case of your app. You should consider both accuracy and battery life when making this decision. Refer to the Apple documentation for details of how to configure location services.

Once you have created a CLLocationManagerDelegate you can use the helper method in the Kumulos SDK to send location updates to Kumulos.

[Kumulos.shared sendLocationUpdate:location];

iBeacon Detection

You can send Kumulos beacon proximity updates and use these to trigger automations such as sending a push notification when an install is in proximity to a beacon.

How you configure the Core Location services for iOS depends on the use case for your app and you should consider throttling how regularly these events are transmitted for both battery life and network traffic.

The Kumulos SDK provides a helper method to notify our services of proximity to a detected beacon.

[Kumulos.shared sendiBeaconProximity:beacon];

Push

The Kumulos SDK provides push notifications with APNS. To integrate Kumulos Push into your iOS project, you have to complete the following steps:

  1. Set up APNS certificates for your app
  2. Configure Push in the Kumulos Agency Console
  3. Integrate SDK components with your iOS project
  4. Register for push form the client app

Configuration & Integration

In order to send push notifications to iOS devices with Kumulos, you'll need to create certificates in the Apple Developer Member Center. The steps to complete this are shown in the video guide.

Configuring APNS for iOS with Kumulos

After configuring your project, continue to follow the steps below.

Requesting & Registering a Push Token

When you consider it appropriate, you need to request permission from the user to send them push notifications.

Whilst you can handle this yourself, Kumulos provides a convenience method:

// Assuming you initialized the Kumulos object using [Kumulos initializeWithConfig:]
[[Kumulos shared] pushRequestDeviceToken];

This will prompt the user to accept push notifications with the badge, alert, and sound settings. When the user accepts, your app delegate will receive the push token.

Once you have the device token, you will need to register this token with the current Kumulos installation like so:

- (void) application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    [[Kumulos shared] pushRegisterWithDeviceToken:deviceToken];
}

Checking Push Registrations

When you run your app on a simulator or install your app on a device, you can check that the install has successfully registered for push notifications by selecting the app and clicking the Installs tab to see the ten most recent installs of your app. Click on any install and then click on Push tab.

Install push details

These steps are all that are required to register the device for push notifications. To track & report on push conversions, you need to track the push opens.

Track Push Conversions

When your app receives a push notification, or is launched by tapping on a push notification, your app's delegate will be called back by the OS.

To correctly report on push open rates & conversions, you need to tell Kumulos that the app was launched by the remote notification.

For iOS 9 & 10, this is handled slightly differently. In our example, we show an application delegate that can respond correctly on both iOS 9 & 10.

//
//  AppDelegate.m
//

#import "AppDelegate.h"

// Import the Kumulos framework
@import KumulosSDK;
// Used for push on iOS10
@import UserNotifications;

static NSString* const K_API_KEY = @"YOUR_API_KEY";
static NSString* const K_SECRET_KEY = @"YOUR_SECRET_KEY";

// Conform to the UNUserNotificationCenterDelegate protocol to handle push on iOS10
@interface AppDelegate () <UNUserNotificationCenterDelegate>

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Initialize the Kumulos SDK
    KSConfig *config = [KSConfig configWithAPIKey:K_API_KEY andSecretKey:K_SECRET_KEY];
    [Kumulos initializeWithConfig:config];

    // Register the app delegate as the notification center handler
    [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self];

    return YES;
}

...

// iOS9 handler for push notifications
// iOS9+10 handler for background data pushes (content-available)
- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateInactive) {
        [[Kumulos shared] pushTrackOpenFromNotification:userInfo];
    }

    completionHandler(UIBackgroundFetchResultNoData);
}

// Called on iOS10 when your app is in the foreground to allow customizing the display of the notification
- (void) userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
    completionHandler(UNNotificationPresentationOptionNone);
}

// iOS10 handler for when a user taps a notification
- (void) userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler {
    NSDictionary* userInfo = [[[[response notification] request] content] userInfo];
    [[Kumulos shared] pushTrackOpenFromNotification:userInfo];

    completionHandler();
}

@end

This sample shows how to track push open conversions when a user has tapped on a notification and launched the app. However, you may wish to customize when you want to track a push open. In those cases, you can simply call pushTrackOpenFromNotification as desired and pass in the userInfo object from the notification.

Handling URL Pushes

When you send a URL push with Kumulos, you will need to handle opening the browser from your delegate methods like so:

// iOS9 handler for push notifications
// iOS9+10 handler for background data pushes (content-available)
- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateInactive) {
        [[Kumulos shared] pushTrackOpenFromNotification:userInfo];

        // Handle opening URLs on notification taps
        NSURL* url = [NSURL URLWithString:userInfo[@"custom"][@"u"]];
        if (url) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [[UIApplication sharedApplication] openURL:url];
            });
        }
    }

    completionHandler(UIBackgroundFetchResultNoData);
}

// iOS10 handler for when a user taps a notification
- (void) userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler {
    NSDictionary* userInfo = [[[[response notification] request] content] userInfo];
    [[Kumulos shared] pushTrackOpenFromNotification:userInfo];

    // Handle URL pushes
    NSURL* url = [NSURL URLWithString:userInfo[@"custom"][@"u"]];
    if (url) {
        [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:^(BOOL success) {
            /* noop */
        }];
    }

    completionHandler();
}

Handling Background Data Pushes

When you send a background data push with Kumulos, the content-available flag will be set on the notification.

This will allow your app to be woken up to process the push notification in the background.

Make sure you have enabled the "Remote notifications" background mode in your project's "Capabilities" settings

The background push notification will trigger the application:didReceiveRemoteNotification:fetchCompletionHandler: application delegate:

- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    // iOS9 handle push opens
    if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateInactive) {
        [[Kumulos shared] pushTrackOpenFromNotification:userInfo];
    }

    // userInfo[@"aps"][@"content-available"] will be set to 1
    // userInfo[@"custom"][@"a"] will contain any additional data sent with the push

    completionHandler(UIBackgroundFetchResultNoData);
}

Note if you set a title & message then a notification will be shown to the user. If you want the push to be silent, don't set a title or message when sending.

Push Channels

You can create and manage subscriptions to push notification Channels via the SDK. Channels allow your users to define preferences for the type of content they would like to receive via notifications.

You can manage all channel interactions via the KumulosPushSubscriptionManager class.

KumulosPushSubscriptionManager* manager = [[KumulosPushSubscriptionManager alloc] initWithKumulos:[Kumulos shared]];

The push manager exposes several methods to manage subscriptions:

@interface KumulosPushSubscriptionManager

- (void) subscribeToChannels:(NSArray<NSString*>* _Nonnull) uuids onComplete:(KSPushSubscriptionCompletionBlock) complete;

- (void) unsubscribeFromChannels:(NSArray<NSString*>* _Nonnull) uuids onComplete:(KSPushSubscriptionCompletionBlock) complete;

- (void) setSubscriptions:(NSArray<NSString*>* _Nonnull) uuids onComplete:(KSPushSubscriptionCompletionBlock) complete;

- (void) clearSubscriptions:(KSPushSubscriptionCompletionBlock) complete;

- (void) listChannels:(KSPushChannelsSuccessBlock) complete;

- (void) createChannelWithUuid:(NSString* _Nonnull) uuid shouldSubscribe:(BOOL) subscribe name:(NSString* _Nullable) name showInPortal:(BOOL) shownInPortal andMeta:(NSDictionary* _Nullable) meta onComplete:(KSPushChannelSuccessBlock) complete;

@end

Results are handled by passing a block for the operation's completion like so:

[self.manager listChannels:^(NSError * _Nullable error, NSArray<KSPushChannel *> * _Nullable channels) {
    if (error) {
        // Handle any error as you want
        return;
    }

    // Handle channel listing result
}];

When creating channels, the visiblity and meta data can be controlled. Please make sure you understand these concepts when creating channels from the SDK.

Build

This section shows various examples of calling your Kumulos API methods using the SDK or the generated bindings, and handling results with blocks or delegates as desired.

To talk to Kumulos, you need three things:

  • your method title (in lowerCamelCase)
  • a method parameters map (field names are also in lowerCamelCase)
  • some response handler code

All networking is performed asynchronously, but your result handling blocks & delegates will always be called back on the application's main thread

Using the SDK

Handling Results with Blocks

To make calls to your API methods, use the callMethod:... selectors on the Kumulos class. For an example using blocks to handle the response:

NSDictionary* params = @{@"author": @"Jane Austin"};
// Assuming Kumulos instance has been initialized as shown in the Initialization section
[[Kumulos shared] callMethod:@"getAllBooksByAuthor" withParams:params success:^(KSAPIResponse * _Nonnull response, KSAPIOperation * _Nonnull op) {
    NSArray* books = response.payload;

    // Do something with the books
  } andFailure:^(NSError * _Nonnull err, KSAPIOperation * _Nonnull op) {
    NSLog(@"Something went wrong!");
  }];

Handling Results with Delegates

Alternatively you can handle results using a delegate conforming to the KSAPIOperationDelegate protocol:

@import KumulosSDK;

@interface ViewController () <KSAPIOperationDelegate>

@property (nonatomic) Kumulos* k;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.k = [[Kumulos alloc] initWithAPIKey:@"YOUR_API_KEY" andSecretKey:@"YOUR_SECRET_KEY"];
    [self.k [email protected]"getAllAuthors" withDelegate:self];
}

- (void) operation:(KSAPIOperation *)operation didCompleteWithResponse:(KSAPIResponse *)response {
    NSString* methodAlias = operation.method;
    NSLog(@"%@", response.payload);

    // Check which method it was and handle result as appropriate
}

// Optionally conform to handle errors
- (void) operation:(KSAPIOperation *)operation didFailWithError:(NSError *)error {
    NSLog(@"Something went wrong!");
}

@end

Cancelling API Operations

To cancel requests, just grab the KSAPIOperation* from the Kumulos object and call the cancel selector. For example:

KSAPIOperation* theOperation = [[Kumulos shared] callMethod:@"myNotSoAwesomeOperation" withDelegate:self];
// Change your mind
[theOperation cancel];

Using the Generated Bindings

If you are using the generated bindings, each of your API methods will be available to invoke directly on the Kumulos class thanks to the Kumulos (Bindings) category implemented by Kumulos.m.

Integration

Using the generated bindings assumes that you have deployed an API for iOS/OSX and downloaded the libraries.

After downloading the libraries, to make use of the generated bindings, drag & drop the Kumulos.h and Kumulos.m files from the download into your project.

Add the files to your project

Finally, you should install the KumulosSDK core framework from CocaPods, Carthage, or sources (instructions on GitHub).

Initialization

After integration, you can use the generated init method that has the keys provided:

#import "Kumulos.h"
...
Kumulos* k = [[Kumulos alloc] init];

Example

In this mode of operation, you can only handle the reults using a delegate conforming to the KumulosDelegate protocol defined in Kumulos.h.

For example:

#import "Kumulos.h"

@interface ViewController () <KumulosDelegate>

@property (nonatomic) Kumulos* k;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.k = [[Kumulos alloc] initWithAPIKey:@"YOUR_API_KEY" andSecretKey:@"YOUR_SECRET_KEY"];

    // Tell Kumulos which delegate will handle API operation results
    [self.k setDelegate:self];
    // Invoke the API method directly
    [self.k getAllAuthors];
}

- (void) kumulosAPI:(Kumulos*)kumulos apiOperation:(KSAPIOperation*)operation getAllAuthorsDidCompleteWithResult:(NSArray*)theResults {
    // Handle the results for the getAllAuthors API call
}

// Optionally handle errors
- (void) kumulosAPI:(kumulosProxy *)kumulos apiOperation:(KSAPIOperation *)operation didFailWithError:(NSString *)theError {
    ...
}

@end

Crash

Crash reporting allows you to track unhandled exceptions in your app, and optionally log any caught exceptions for further investigation. Crash reporting is not enabled by default, to enable this feature, simply modify your Kumulos initialization to include enabling crash reporting.

KSConfig *config = [KSConfig configWithAPIKey:@"YOUR_API_KEY" andSecretKey:@"YOUR_SECRET_KEY"];
[config enableCrashReporting];
[Kumulos initializeWithConfig:config];

Unhandled exceptions that lead to crashes will now be recorded automatically and shown in your App Delivery Console.

Note that crash reporting is not available whilst connected to a debugger, but it does work with debug builds.

If you wish to log any caught exceptions, you can record them with the helper method:

[[Kumulos shared] logExceptionWithName:@"name" reason:@"reason" language:@"objc" lineNumber:@"1" stackTrace:@[@"boom", @"clunk", @"bang"] loggingAllThreads:YES];

Ensure that crash reporting is enabled when attempting to log exceptions.

Upload dSYM Files for Symbolication

In order to ensure your stack traces are fully symbolicated, it is necessary to upload your dSYM information from your project.

To upload dSYM files for Release builds, you should add a new "Run Script Phase" to your Xcode project's "Build Phases".

For CocoaPods installations, use this script:

"$PODS_ROOT/KumulosSdkObjectiveC/upload_dsyms.sh" API_KEY SERVER_KEY

For Carthage installations, use this script:

"$SRCROOT/Carthage/Checkouts/KumulosSdkObjectiveC/upload_dsyms.sh" API_KEY SERVER_KEY

Copy the script snippet into a new Run Script Phase as shown below:

Add a new Run Script Build Phase

Make sure you replace the API_KEY and SERVER_KEY tokens with your app's API and server keys which can be found on the application's dashboard.

Bitcode-enabled Builds

If your project has bitcode enabled, it will be necessary to upload the dSYM files that Apple generates as part of App Store distribution step.

These generated dSYM files can be downloaded using the Xcode Organizer. Once downloaded, the generated dSYM files will be available in your app's archive.

You should follow the steps of the screen capture below to prepare your symbols for upload.

Fetching & preparing generated dSYMs for upload

Once prepared for upload, you should upload the dSYM zip file to your App Delivery Console.

Changelog

1.6.0

  • Add iBeacon proximity helper
  • Refactor most server interactions to be recorded in the events DB
  • Fix Podspec missing resources file

1.5.3

  • Add dSYM upload script to SDK distribution

1.5.3

  • Record background event synchronously

1.5.1

  • Prevent intermittent build failures by explicitly referencing dependency targets from the SDK schemes

1.5.0

  • Add custom event tracking
  • Capture session analytics events
  • Add user association helper

1.4.1

  • Add public header file to Podspec

1.4.0

  • Add Crash Reporting

1.3.2

  • Version bump

1.3.1

  • Expose geolocation update helper publicly
  • Fix nil reference exception in stats

1.3.0

  • Add geolocation update helper method
  • Add locale & bundle to stats payload

1.2.1

  • Add missing header guard for macOS

1.2.0

  • Add Podspec for CocoaPods support
  • Update installation instructions
  • Fix include guards for iOS & macOS headers

1.1.0

  • Add push channel subscription management

1.0.1

  • Fix compilation of framework to include full bitcode

1.0.0

  • Initial release of SDK as a framework
  • Initial release of support for Kumulos Push
  • Clean up of public interfaces & properties
  • Move supporting behavior for generated bindings from the core SDK to the generated bindings
  • Modernize Objective-C throughout
  • Utilize latest AFNetworking throughout

The generated bindings code has been updated to be compatible with our latest SDK whilst aiming to maintain backwards compatibility with the most commonly used features of the older SDKs. However, if you have any trouble upgrading, please drop a line to [email protected] -- we'd be happy to help.