Kumar, Anish

Mobile App Performance (MAP) SDK Integration Guide - ReactNative (iOS)

Instructions for adding the Mobile App Performance(MAP) SDK to your ReactNative app for iOS.

Required For Setup

  • You must be an Akamai Ion customer to use this SDK. If you are not on ION and would like to know more, please reach out to us at specialist@akamai.com
  • MAP SDK License Key - Follow the steps shared in our getting started guide to activate MAP SDK from the marketplace.

 

Example Project

For your convenience, we've created a Sample App/project that you can use to get started and learn about using MAP SDK with ReactNative.

MAP SDK being a natively designed SDK does not directly support React Native to intercept traffic. To overcome the challenge we need to use the SDK with Custom NSURLSession.

 

Add MAP SDK Native framework

1. Create React Native project using CLI

user$ react-native init MAPReactUnified

2. Navigate to the iOS folder from the project directory created above.

img1

 

3. Add pod 'Aka-MAP' in the pod file.

4. Run pod install to add MAP and AkaCommon framework to the project.

5. If using MAP from Swift, you must add the following line to the Objective-C bridging header ([project name]-Bridging-Header.h):

#import <AkaMap/AkaMap.h>
#import <AkaCommon/AkaCommon.h>

6. Add license in Info.plist

img 2

7. Initialize MAP SDK

Objective C

[AkaCommon configure];

Swift

AkaCommon.configure()

Intercept Traffic with MAP SDK

It's important to ensure all your network requests from the mobile app are intercepted by MAP SDK to apply optimizations from each feature. By default, MAP SDK intercepts traffic going from NSURLSession. Wrappers are required when working with custom NSURLSession or third-party libraries.

React Native uses Custom NSURLSession to handle network requests in RCTHTTPRequestHandler class. We need to modify this class to handle custom NSURLSession.

Follow the steps below to handle this:

 

1. Search for RCTHTTPRequestHandle in project finder

img 3

2. Open RCTHTTPRequestHandler.h

3. Add property configuration

@property (nonatomic, strong) NSURLSessionConfiguration *configuration;

4. Open RCTHTTPRequestHandler.mm

5. Modify - (BOOL)canHandleRequest:(NSURLRequest *)request

6. Initialise NSURLSessionConfiguration and add local notification.

7. Notification is one of the approaches to intercept, if you are comfortable with another approach, you can do so.

self.configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
//Notify MAP SDK
NSDictionary *dict = [NSDictionary dictionaryWithObject:self.configuration     forKey:@"config"];
[[NSNotificationCenter defaultCenter] postNotificationName: @"MAP_Notification"   object:nil userInfo:dict];

8. Modify - (NSURLSessionDataTask *)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate

9. Comment or remove -  NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];

10. Modify all instances of configuration to self.configuration

//NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    // Set allowsCellularAccess to NO ONLY if key ReactNetworkForceWifiOnly exists AND its value is YES
    if (useWifiOnly) {
      self.configuration.allowsCellularAccess = ![useWifiOnly boolValue];
    }
    [self.configuration setHTTPShouldSetCookies:YES];
    [self.configuration setHTTPCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
    [self.configuration setHTTPCookieStorage:[NSHTTPCookieStorage       sharedHTTPCookieStorage]];
    _session = [NSURLSession sessionWithConfiguration:self.configuration
                  delegate:self  delegateQueue:callbackQueue];

11. Add Observer

 

Objective C

Open AppDelegate.h class and in didFinishLaunchingWithOptions add addObserver code

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(triggerInterception:) name:@"MAP_Notification" object:nil];

Add triggerInterception code

-(void) triggerInterception:(NSNotification *) notification
{
    NSDictionary *dict = notification.userInfo;
    NSURLSessionConfiguration *sessionConfig = [dict valueForKey:@"config"];
    if (sessionConfig != nil) {
        [[AkaCommon shared] interceptSessionsWithConfiguration:sessionConfig];
    }
}

 

Swift

Add observer code in class AppDelegate::

NotificationCenter.default.addObserver(self, selector:
#selector(self.triggerInterception(notification:)), name:
Notification.Name("MAP_Notification"), object: nil)

 

Handle notification:

@objc func triggerInterception(notification: Notification) {
    let dict = notification.userInfo
    guard let sessionConfig = dict?["config"] else
    {
      return
    }
    AkaCommon.shared().interceptSessions(with: sessionConfig as!        URLSessionConfiguration)
  }

 

Add React Native bridging class

If you want to invoke MAP SDK APIs from React Native, you will need to add a bridging class as explained below.

 

NOTE: This step is not mandatory for MAP SDK optimizations to work. However, you may need to do this in order to use API based MAP SDK behaviors such as custom events.

If you are not using any MAP SDK local APIs, you could skip the next step and move to iOS Sample App: section

 

Steps for Bridging

1. Open your XCode Workspace project

2. Add a new file from the project menu: File -> New -> File

3. Chose the template as iOS -> Cocoa TouchAdd React Native bridging class. If you want to call MAP API from React Native, you need to add this Bridging class.

Steps for Bridging

  • Open your XCode Workspace project
  • Add a new file from the project menu: File -> New -> File
  • Chose the template as iOS -> Cocoa Touch Class and click on Next

 

4. Name the class as needed. For this exercise, we will name it as "MAPManager"

5. Add RCTBridgeModule.h to MAPManager class.

6. MAPManager.h code

#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
@interface MAPManager : NSObject<RCTBridgeModule>

7. Open your MAPManager.m

8. Your class must also include the RCT_EXPORT_MODULE() macro. This takes an optional argument that specifies the name that the module will be accessible in your JavaScript code.

RCT_EXPORT_MODULE();

9. Now we need to expose the method we need in order to use MAP services from our JavaScript code. For this example we'll be using the MAP startEvent method to add the event name. To do so, we need to use another macro provided by React Native: RCT_EXPORT_METHOD.

RCT_EXPORT_METHOD(startEvent:(NSString *)eventName )
{
  RCTLogInfo(@"MAP startEvent %@", eventName);
  [[AkaMap shared] startEvent:eventName];
}

10. Complete  MAPManager.m code:

#import <AkaCommon/AkaCommon.h>
#import <AkaMap/AkaMap.h>

@interface MAPManager ()
@end

@implementation MAPManager
RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(startEvent:(NSString *)eventName )
{
  RCTLogInfo(@"MAP startEvent %@", eventName);
  [[AkaMap shared] startEvent:eventName];
}
RCT_EXPORT_METHOD(stopEvent:(NSString *)eventName )
{
  RCTLogInfo(@"MAP stopEvent %@", eventName);
  [[AkaMap shared] stopEvent:eventName];
}
@end

 

Call bridge method from React Native code

1. Open app.js 

2. Add import for handling native method calls:

import {NativeModules} from 'react-native';

3. Add addDimension bridged method, which was added in Objective-C to JS

NativeModules.MPAPManager.startEvent('ViewLoaded')

4. With this approach, native method will be accessed from JS

5. Run the app from CLI - react-native run-ios

 

iOS Sample App:

In the sample app, I am Pre-Positioning images. Once the app is compiled and installed, please wait for a few moments for pre-positioning to kick in and download images.

Then tap on the Image button to show 21 images in a grid. The images will be fetched from the device cache and shown in the UI.

img 5

 

Initial UI

 

img 7

 

Images loaded from Cache.

 

img 8

 

In the above image, one could see the manifest being received in Charles proxy logs, then the images are being fetched.

Once an object/response is prepositioned, we will not see any new network request in Charles-Proxy for the same. 

 

Pre-Positioned Images intercepted by MAP SDK

img 9

 

 

API request intercepted by MAP SDK:

img 10