Skip to main content

Overview

Permutive does not provide an official React Native library, but you can utilise our native Android and iOS SDKs from within your React Native application. This involves creating Android and iOS Native Modules for communicating from JS to our Native SDKs. The full guide from React Native can be found here. The functions in the modules created for both platforms must share equivalent function signatures to allow the JS code to call a single function which will work on both platforms.

Android Native Module

The Android Native Module uses our native Android SDK, the full documentation for this can be found here. It’s recommended to use Android Studio for developing the Android native parts of your React Native application. It provides code syntax feedback, errors and code completion making it much easier to write your Android code. You can download the latest version here. Start by importing the project into Android Studio from your android/app folder.

Add the Permutive Dependency

To start with, add the Permutive dependency to your Android app module in your React Native project. To do this add the dependency to the build.gradle which you can find in the android/app folder in your project.
dependencies {
  // ...
  implementation("com.permutive.android:core:<version>")
  // ...
}

Create the Permutive Native Module file

Then create a native module for Permutive in the Android app source code, this should be at android/app/src/main/java/<your-app-package>/. In this example the file is called PermutiveModule.kt. This file provides the bridging between the JS functions and the Native Android functions. It extends ReactContextBaseJavaModule. Each function you would like to expose in JS must be annotated with @ReactMethod. The bridging code can only return and receive specific types when interfacing with JS, for full details see the React Native official documentation for Android Native Modules here. The following sample code demonstrates a Native Module which exposes a subset of the Permutive SDK functionality to JS:
PermutiveModule.kt
package com.permutive.reactnativesample

import android.net.Uri
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.WritableArray
import com.facebook.react.bridge.WritableMap
import com.permutive.android.EventProperties
import com.permutive.android.PageTracker
import com.permutive.android.Permutive
import java.util.UUID

class PermutiveModule(
    private val reactContext: ReactApplicationContext
) : ReactContextBaseJavaModule(reactContext) {
    override fun getName(): String = "PermutiveModule"
    private lateinit var permutive: Permutive
    private var pageTracker: PageTracker? = null

    @ReactMethod(isBlockingSynchronousMethod = true)
    fun initializePermutive(
        workspaceId: String,
        apiKey: String,
    ) {
        permutive = Permutive(
            context = reactContext,
            workspaceId = UUID.fromString(workspaceId),
            apiKey = UUID.fromString(apiKey),
        )
    }

    @ReactMethod
    fun trackPageView(
        title: String?,
        url: String?,
        referrer: String?,
        eventProperties: ReadableMap?
    ) {
        pageTracker = permutive.trackPage(
            title = title,
            url = url?.let { Uri.parse(it) },
            referrer = referrer?.let { Uri.parse(it) },
            eventProperties = eventProperties?.let {
                EventProperties.from(
                    *it.toHashMap().map { (k, v) -> k to v }.toTypedArray()
                )
            }
        )
    }

    @ReactMethod
    fun trackPageviewComplete() {
        pageTracker?.close()
    }

    @ReactMethod(isBlockingSynchronousMethod = true)
    fun currentCohorts(): WritableArray {
        val array = Arguments.createArray()
        permutive.currentCohorts.forEach { array.pushString(it) }
        return array
    }

    @ReactMethod(isBlockingSynchronousMethod = true)
    fun currentActivations(): WritableMap {
        val map = Arguments.createMap()
        permutive.currentActivations.forEach { (activationId, cohorts) ->
            val array = Arguments.createArray()
            cohorts.forEach { array.pushString(it) }
            map.putArray(activationId, array)
        }
        return map
    }

    @ReactMethod
    fun listenForCohortUpdates(callback: Callback) {
        permutive.triggersProvider().cohorts { cohorts ->
            val array = Arguments.createArray()
            cohorts.forEach { array.pushString(it) }
            callback.invoke(array)
        }
    }
}

Register the Native Module

On Android you need to register the Native Module with the Android application to allow the JS code to call it. To do this create a ReactPackage for your application where you can add your new Native Module. First create a file in your android/app/src/main/java/<your-app-package>/ folder, in this example it’s called PermutiveAppPackage.kt. Create a class which extends ReactPackage and override the createNativeModules method and add your Native Module to the list returned by the function. The following sample code demonstrates this:
PermutiveAppPackage.kt
package com.permutive.reactnativesample

import android.view.View
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ReactShadowNode
import com.facebook.react.uimanager.ViewManager

class PermutiveAppPackage : ReactPackage {
    override fun createViewManagers(
        reactContext: ReactApplicationContext
    ): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf()

    override fun createNativeModules(
        reactContext: ReactApplicationContext
    ): MutableList<NativeModule> = listOf(PermutiveModule(reactContext)).toMutableList()
}
You then need to add this package to the list of packages returned by the ReactNativeHost’s getPackages function. This can be found in the MainApplication file in your android/app/src/main/java/<your-app-package>/ folder. The following sample code shows this:
MainApplication.kt
package com.permutive.reactnativesample

// ...

class MainApplication : Application(), ReactApplication {

  override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper(
        this,
        object : DefaultReactNativeHost(this) {
          override fun getPackages(): List<ReactPackage> {
            // Packages that cannot be autolinked yet can be added manually here, for example:
            // packages.add(new MyReactNativePackage());
            return PackageList(this).packages.apply {
              // Add your custom package here
              add(PermutiveAppPackage())
            }
          }
          // ...
      }
  )
  // ...
}
Your Android native code is now callable from JS! How to do this is explained in a section below.

iOS Native Module

The iOS Native Module sample uses our native iOS SDK, the full documentation for this can be found here. It’s recommended to use Xcode for developing the iOS native parts of your React Native application. It provides code syntax feedback, errors and code completion making it much easier to write your iOS code. You can download the latest version here. Start by importing the iOS part of the React Native project into Xcode. The easiest way to do this is by navigating to your React Native project folder and opening the ios/<your-app-name>.xcworkspace file.

Add the Permutive dependency

Add the Permutive iOS dependency to your project using Swift Package Manager. To do this, go to File -> Add Package Dependencies. Then enter https://github.com/permutive-engineering/permutive-ios-spm in the Search or Enter Package URL bar, on the top right, then select Add Package, select Package Product Permutive_iOS and add to your React Native app Target, then select Add Package.

Create the Native Module files

This guide uses Swift files which interact with the Permutive SDK and Objective-C bridging to connect these to JS. Add a new Swift file to your project in the ios/ folder. This should be the same name as your Android module file if you’ve already created that, for this sample we’ll use ‘PermutiveModule’ again so we create PermutiveModule.swift. Your project should have an Objective-C bridging header file which you can find at ios/<your-app-name>/<your-app-name>-Bridging-Header.h. You need to import the ReactBridgeModule in that file as shown here:
<your-app-name>-Bridging-Header.h
//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "React/RCTBridgeModule.h"
You’ll also need an Objective-C file which is used to expose the Swift methods to JS. Create a new Objective-C file with the same name as your Native Module file in the ios/ folder. In this sample that is PermutiveModule.m. The Swift class needs to be annotated as an Objective-C exposed class and the methods need to be annotated too. Only specific types can be used when receiving parameters and returning from functions for communicating with JS. For full details on this see the official React Native iOS Native Modules documentation here. This sample shows a Swift module with the equivalent functionality to the Android Native Module:
PermutiveModule.swift
import Foundation
import Permutive_iOS

@objc(PermutiveModule)
class PermutiveModule: NSObject {
  private var pageTracker: PageTrackerProtocol? = nil
  @objc
  func initializePermutive(
    _ workspaceId: NSString,
    _ key: NSString
  ) {
    guard let options = Options(
      apiKey: key as String,
      organisationId: workspaceId as String,
      workspaceId: workspaceId as String
    ) else {
      print("Error creating Permutive Options")
      return
    }
    Permutive.shared.start(with: options) { error in
      guard error != nil else {
        return
      }
      print("Error starting Permutive: \(String(describing: error))")
      return
    }
  }

  @objc
  func trackPageView(
    _ title: NSString?,
    _ url: NSString?,
    _ referrer: NSString?,
    _ eventProperties: NSDictionary?
  ) {
    do {
      // Convert NSDictionary to EventProperties
      let properties = try EventProperties(
          (eventProperties as? [String: Any])?
            .compactMapValues { propertyName in
              propertyName as? AnyHashable
            }.reduce(into: [:]) { outputDictionary, inputDictionary in
              outputDictionary[inputDictionary.key] = inputDictionary.value
            } ?? [:]
      )

      let context = (title != nil && url != nil && referrer != nil) ? Context(
          title: title! as String,
          url: URL(string: url! as String)!,
          referrer: URL(string: referrer! as String)!
      ) : nil

      pageTracker = try Permutive.shared.createPageTracker(
          properties: properties,
          context: context
      )
    } catch {
      print("Error starting page tracker: \(String(describing: error))")
    }
  }

  @objc
  func trackPageviewComplete() {
    pageTracker?.stop()
  }

  @objc
  func currentCohorts() -> NSArray {
    return Array(Permutive.shared.cohorts) as NSArray
  }

  @objc
  func currentActivations() -> NSDictionary {
    return Permutive.shared.activations as NSDictionary
  }

  @objc
  func listenForCohortUpdates(_ callback: @escaping RCTResponseSenderBlock) {
    let queryRange = Array(<relevant-cohort-ids>).map { queryId in String(queryId) }
    let action = Permutive.shared.triggerProvider?.action(boolFor: Set(queryRange)) { (_, _) in
      callback([Array(Permutive.shared.cohorts) as NSArray])
    }
  }
}
To be able to call this code from JS it requires bridging through Objective-C code. To do this within the Objective-C native module file you created before, you import the same RCTBridgeModule and create an RCT_EXTERN_MODULE interface which references your Swift class. Two Objective-C functions are used to expose the Swift methods in this example:
  • RCT_EXTERN_METHOD - Exposes an asynchronous method to JS. It takes an argument in the following form: functionName:(Parameter1Type)parameter1InternalName :(Parameter2Type)parameter2InternalName.
  • RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD - Exposes a synchronous method to JS. It takes arguments in the same form as RCT_EXTERN_METHOD.
The following sample shows the Objective-C code used to expose the sample Swift class:
PermutiveModule.m
#import <Foundation/Foundation.h>

#import "React/RCTBridgeModule.h"

@interface RCT_EXTERN_MODULE(PermutiveModule, NSObject)

RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(initializePermutive:(NSString)workspaceId :(NSString)key)
RCT_EXTERN_METHOD(trackPageView:(NSString)title :(NSString)url :(NSString)referrer :(NSDictionary)eventProperties)
RCT_EXTERN_METHOD(trackPageviewComplete)
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(currentCohorts)
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(currentActivations)
RCT_EXTERN_METHOD(listenForCohortUpdates:(RCTResponseSenderBlock)callback)

@end
Your iOS code is now ready to be called from JS!

Calling Native code from JS

To consume your native code from JS you can create a wrapper module around your native module. Create a new JS file in your app/ folder with the same name as your Native Module files. In this sample that is PermutiveModule.js with the following content:
PermutiveModule.js
import {NativeModules} from 'react-native';
const {PermutiveModule} = NativeModules;
export default PermutiveModule;
You can then import this module into your React files and call into the native functions. This sample shows calling into the PermutiveModule in the useEffect function to initialize the Permutive SDK, track a Pageview, set the latest cohorts to some state and also listen for changes:
import PermutiveModule from '../PermutiveModule'

export default function HomeScreen() {
  const [currentCohorts, setCurrentCohorts] = useState('');
  useEffect(() => {
    PermutiveModule.initializePermutive("e0039147-51e7-4224-a814-0e2d438aabcd", "da4d09b5-843a-4bd5-bd79-8cea7f69f730");
    PermutiveModule.trackPageView("Page title", null, null, null);
    setCurrentCohorts(PermutiveModule.currentCohorts().join(', '));
    PermutiveModule.listenForCohortUpdates((cohorts: any) => setCurrentCohorts(cohorts.join(', ')))
  }, [])
  return (
    // ...
  )
}
You can utilise these functions to implement Permutive within your app. If you require more functionality you can add it by creating new functions in the React Native Modules for each platform which call the relevant Permutive SDK functions. Your React Native app is now ready to finish your Permutive integration.