OptiMobile SDK Reference

This guide provides documentation for the API of the Optimove SDK across all supported mobile platforms. It is broken down by functional area and includes initialization, analytics, tracking events, push notifications, in-app messaging, and deferred deep linking.

If you would prefer to follow a more step-by-step guide for integration please refer to Getting Started on Mobile.

Initialization

Standard initialization

If you know your credentials when your application starts, your initialization should look as follows:

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        let config = OptimoveConfigBuilder(
          optimoveCredentials: "<YOUR_OPTIMOVE_CREDENTIALS>", 
          optimobileCredentials: "<YOUR_OPTIMOVE_MOBILE_CREDENTIALS>")
            .build()

        Optimove.initialize(with: config)
    }
}
public class YourApplication extends Application {

  @Override
  public void onCreate() {
    super.onCreate();

    Optimove.initialize(this, new OptimoveConfig.Builder(
        "<YOUR_OPTIMOVE_CREDENTIALS>", 
        "<YOUR_OPTIMOVE_MOBILE_CREDENTIALS>"
    ).build());
  }
}


/********************** Environments **********************
If you are using staging and prod environments, you will receive 2 different sets of credentials. 
Consider using your BuildConfig file to declare them: */

buildTypes {
        release {
            ...
            buildConfigField "String", "OPTIMOVE_CREDENTIALS", '"<YOUR_OPTIMOVE_CREDENTIALS>"'
            buildConfigField "String", "OPTIMOVE_MOBILE_CREDENTIALS", '"<YOUR_OPTIMOVE_MOBILE_CREDENTIALS>"'
        }
        stg {
            ...
            buildConfigField "String", "OPTIMOVE_CREDENTIALS", '"<YOUR_OPTIMOVE_STAGING_CREDENTIALS>"'
            buildConfigField "String", "OPTIMOVE_MOBILE_CREDENTIALS", '"<YOUR_OPTIMOVE_MOBILE_STAGING_CREDENTIALS>"'
        }
    }
    

// Then initialize Optimove using:

 Optimove.initialize(this, new OptimoveConfig.Builder(
        BuildConfig.OPTIMOVE_CREDENTIALS,
        BuildConfig.OPTIMOVE_MOBILE_CREDENTIALS
    ).build());

/********************** Android **********************

Add the initialization code to your Application extending class onCreate method (usually 
located in android/app/src/main/java/.../MainApplication.java): */

import com.optimove.android.OptimoveConfig;
import com.optimove.reactnative.OptimoveReactNativeConfig;
import com.optimove.reactnative.OptimoveReactNativeInitializer;


OptimoveReactNativeInitializer.initializeOptimove(
    OptimoveReactNativeConfig.newInstance()
                                .optimoveCredentials("<YOUR OPTIMOVE CREDENTIALS>")
                                .optimobileCredentials("<YOUR OPTIMOBILE CREDENTIALS>")
                                .deeplinkEnabled(boolean)
                                // OptimoveConfig.InAppConsentStrategy.EXPLICIT_BY_USER | OptimoveConfig.InAppConsentStrategy.AUTO_ENROLL
                                .enableInAppWithConsentStrategy(OptimoveConfig.InAppConsentStrategy)
                                // Optionally change the notification small icon - .setPushSmallIconId(getApplicationInfo().icon)
                                .build(), getApplicationContext());


/************************ iOS ************************

In your ios/AppDelegate application:didFinishLaunchingWithOptions method */

// For Objective-C: 

#import <OptimoveReactNative/OptimovReactNative-Bridging.h>
...
[OptimoveInitializer initialize:@"<YOUR OPTIMOVE CREDENTIALS>" optimobileCredentials:@"<YOUR OPTIMOBILE CREDENTIALS>" inAppConsentStrategy:@"auto-enroll|in-app-disabled|explicit-by-user" enableDeferredDeepLinking:BOOL];


// For Swift:

import OptimoveReactNative
...
OptimoveInitializer.initialize("<YOUR OPTIMOVE CREDENTIALS>", optimobileCredentials: "<YOUR OPTIMOBILE CREDENTIALS>", inAppConsentStrategy: "auto-enroll|in-app-disabled|explicit-by-user", enableDeferredDeepLinking: Bool)

// 1. Create an optimove.json file in your project's root directory with the following:
{
  "optimoveCredentials": "YOUR_OPTIMOVE_CREDENTIALS",
  "optimobileCredentials": "YOUR_OPTIMOVE_MOBILE_CREDENTIALS",
  "inAppConsentStrategy": "in-app-disabled|auto-enroll|explicit-by-user",
  "enableDeferredDeepLinking": false
}

// 2. Declare the asset in your pubspec.yaml:
assets:
  - optimove.json

// 3. In your Dart code, you can now import and use Optimove features:
import 'package:optimove_sdk_flutter/optimove.dart';
// 1. Create an optimove.json file in your project's root directory with the following content:
{
  "optimoveCredentials": "YOUR_OPTIMOVE_CREDENTIALS",
  "optimoveMobileCredentials": "YOUR_OPTIMOVE_MOBILE_CREDENTIALS",
  "inAppConsentStrategy": "in-app-disabled|auto-enroll|explicit-by-user",
  "enableDeferredDeepLinking": false
}

// 2. After you modify optimove.json, don't forget to run:
cordova prepare <platform>

// 3. Finally, if you are using TypeScript, please add the following to your tsconfig.json:
"files": ["./node_modules/@optimove-inc/cordova-sdk/index.d.ts"] 

/********************** Android **********************

Optimove Cordova SDK requires compileSdkVersion 31. Apps created with Cordova 11 by default 
depend on cordova-android@10.*, which sets the default compileSdkVersion to 30. If you are 
getting related errors, you can: */

// 1. Override compileSdkVersion in config.xml:

<platform name="android">
  <preference name="android-targetSdkVersion" value="31" />
</platform>

// 2. Upgrade the dependency:

cordova platform update [email protected]

/*  Please note that the default Android toolchains have moved to JDK11 now. 
You must use JDK11, at least if you change any of the Android API target versions to >=31.

### Other

(Optional) Optimove SDK includes Firebase Bill of Materials. You may override BoM 
version by adding build-extras.gradle to platforms/android/app with the following content: */

ext {
    // Optimove supports [19.0.0, 22.99.99] Firebase Messaging version range,
    // which corresponds to [20.0.0, 28.99.99] Bill of Materials version range.
    OptimoveFirebaseBoMVersion = "28.2.0"
}

/************************ iOS ************************

After you have added the plugin, run the following commands: */

   cd app/platforms/ios
   pod install
   cordova prepare ios

/* 
After first adding the iOS platform, you may need to open the project in 
Xcode and fix any errors if present.

************** Typescript / Javascript **************

In either case, you don't need to import anything from the plugin module. Types are 
available as ambient declarations. For example, in your www/js/index.js, you can 
use the global Optimove object like this: */

document.addEventListener('deviceready', function () {
    if (typeof Optimove === 'undefined') {
      console.info('Optimove plugin not found');
      return;
    }

    Optimove.setPushReceivedHandler(() => {});
}, false);
// Create Assets/OptimoveConfigFiles/optimove.json file with the following content:

  {
    "optimoveCredentials": "YOUR_OPTIMOVE_CREDENTIALS",
    "optimobileCredentials": "YOUR_OPTIMOVE_MOBILE_CREDENTIALS"
  }

/********************** Android **********************

In Player Settings verify the following (it should be set automatically):
- Custom gradle main template set to Plugins/Android/mainTemplate.gradle
- Custom gradle launcher template set to Plugins/Android/launcherTemplate.gradle
- Custom gradle base template set to Plugins/Android/baseProjectTemplate.gradle
- Custom main manifest set to Plugins/Android/AndroidManifest.xml
- For 2020.3+ make sure the custom gradle properties template is set to Plugins/Android/gradleTemplate.properties

************************ iOS ************************

1. Move Artifacts/OptimoveNativeAssets~ from the Artifacts folder to the Assets/ folder. 
The xcframeworks will be automatically added when the project is built.
2. Build the project. Verify that the New Build System in Xcode is enabled.

*********************** Usage ***********************

In your Unity code, you can now import and use Optimove features: */

using OptimoveSdk;

Delayed initialization

This feature allows you to postpone adding Optimove and OptiMobile credentials until a later point in your app's lifecycle, providing you with greater control over when the SDK is fully activated.

let config = OptimoveConfigBuilder(region: .US, features: [.optimove, .optimobile]).build()
Optimove.initialize(with: config)

// time passes...

Optimove.setCredentials(optimoveCredentials: "OPTIMOVE_TOKEN", optimobileCredentials: "OPTIMOBILE_TOKEN")
public class YourApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        OptimoveConfig.FeatureSet desiredFeatures = new OptimoveConfig.FeatureSet()
                .withOptimobile()
                .withOptimove();  

        Optimove.initialize(this, new OptimoveConfig.Builder(OptimoveConfig.Region.EU, desiredFeatures).build());
    }
}

// time passes...

Optimove.setCredentials( "<YOUR_OPTIMOVE_CREDENTIALS>", "<YOUR_OPTIMOVE_MOBILE_CREDENTIALS>");

// After credentials are set, all caches are flushed and the SDK starts behaving identical to 
the Standard Initialisation.
Feature isn't available in this SDK - contact your CSM.
Feature isn't available in this SDK - contact your CSM.
/* include the delayedInitialization block in your optimove.json file, 
setting "enable": true and specifying both the region and the featureSet.
In featureSet, you can control which services are initialized by setting 
enableOptimove and enableOptimobile to true or false as required. */


// Example configuration:
{
  "delayedInitialization": {
    "enable": true,
    "region": "DEV",
    "featureSet": {
      "enableOptimove": true,
      "enableOptimobile": false
    }
  }
}
Feature isn't available in this SDK - contact your CSM.

You can retrieve the region and feature by contacting an Optimove CSM specialist.

Tracking Customers and Events

Tracking screen visits

To track which screens the user has visited in your app, call one of the overloads of reportScreenVisit methods of the Optimove singleton after the SDK has been successfully initialized.

Optimove.shared.reportScreenVisit(
  screenTitle: "Account creation", // Represent the current scene
  screenCategory: "Accounts" // (Optional) Adds the scene category it belongs to
)
Optimove.getInstance()
    .reportScreenVisit(
    "Account creation", // Represent the current scene
    "Accounts" // (Optional) Adds the scene category it belongs to
    );
Optimove.reportScreenVisit(
  screenName: "Account creation", // Represent the current scene
  screenCategory: "Accounts" // (Optional) Adds the scene category it belongs to
);
Optimove.reportScreenVisit(
  screenName: "Account creation", // Represent the current scene
  screenCategory: "Accounts" // (Optional) Adds the scene category it belongs to
);
Optimove.reportScreenVisit(
  "Account creation", // Represent the current scene
  "Accounts" // (Optional) Adds the scene category it belongs to
);
Optimove.Shared.ReportScreenVisit(
  "Account creation", // Represent the current scene
  "Accounts" // (Optional) Adds the scene category it belongs to
);

Tracking customers

You can track your customers by sending the user ID to Optimove during key interactions, such as when they create an account, log in, sign up for a newsletter, or complete a guest checkout.

Difference between Visitors & Customers

Visitor

  • Once the user has downloaded the application and the OptimoveSDK for iOS has run for the first time, the user is considered a visitor, i.e., an unidentified visitor
  • This visitor id is created by the Optimove SDK for each user who enters your application

Customer

  • Once the user creates an account or log in to the application and receives your application unique identifier, they becomes customers
  • This customer ID (also known as SDK ID) is also your Optimove's customer's unique identifier (e.g email address, or numeric id, or etc)
  • All user's visitor data will be stitched to their unique customer ID once recognized

📘

Important information

The SDK ID is also your Optimove's customer's unique identifier (e.g email address, or numeric id, or etc).

Your Optimove customer unique identifier is sent to Optimove on the daily customer transfer.

Any SDK ID that does not correspond/match to your Optimove customer unique identifier (Customer ID) due to faulty / unrecognized SDK IDs will not be tracked.

If you do not know your Optimove customer unique identifier, please contact your Optimove Point of Contact.

Stitching visitors to customers

Once the user successfully creates an account, they become a recognized customer. Use one of the following functions to notify the SDK that the user has a known customer id (SDK ID):

  • setUserId method and pass the SDK ID when an "email address" is not available:
Optimove.shared.setUserId("<MY_SDK_ID>")
Optimove.getInstance().setUserId("<YOUR_CUSTOMERS_SDK_ID>");
Optimove.setUserId("YOUR_CUSTOMERS_USER_ID");
Optimove.setUserId("YOUR_CUSTOMERS_USER_ID");
Optimove.setUserId("YOUR_CUSTOMERS_USER_ID");
Optimove.Shared.SetUserId("YOUR_CUSTOMERS_USER_ID");
  • registerUser method and pass the SDK ID when an "email address" is available
Optimove.shared.registerUser(sdkId: "<MY_SDK_ID>", email: "<MY_EMAIL>")
Optimove.getInstance().registerUser("<YOUR_CUSTOMERS_SDK_ID>", "<YOUR_CUSTOMERS_EMAIL_ADDRESS>");
Optimove.registerUser("YOUR_CUSTOMERS_USER_ID", "YOUR_CUSTOMERS_EMAIL");
Optimove.registerUser("YOUR_CUSTOMERS_USER_ID", "YOUR_CUSTOMERS_EMAIL");
Optimove.registerUser("YOUR_CUSTOMERS_USER_ID", "YOUR_CUSTOMERS_EMAIL");
Optimove.Shared.RegisterUser("YOUR_CUSTOMERS_USER_ID", "YOUR_CUSTOMERS_EMAIL");

📘

Important Note:

We highly recommend apps to send the SDK the SDK ID every time the app starts. It increases the chances of successful tracking of already existing end users. The SDK blocks multiple calls with the same SDK ID and/or Email, so it's perfectly safe to call setUserId/registerUser every time the app starts.

Encrypting SDK ID

If you will be sending encrypted SDK ID, please follow the steps in Reporting encrypted CustomerIDs

Sending emails only to Identified Visitors (non customers)

To send emails to users who are not customers, call the setUserEmail method to notify the SDK that the user has a known email address.

Optimove.shared.setUserEmail(email: "<MY_EMAIL>")
Optimove.getInstance().setUserEmail("<MY_EMAIL>");
Optimove.setUserEmail("YOUR_CUSTOMERS_EMAIL");
Optimove.setUserEmail("YOUR_CUSTOMERS_EMAIL");
Optimove.setUserEmail("YOUR_CUSTOMERS_EMAIL");
Optimove.Shared.SetUserEmail("YOUR_CUSTOMERS_EMAIL");

📘

Important Note:

The SDK blocks multiple calls with the same Email, so it's perfectly safe to call setUserEmail every time the app starts.

Signing out of the app

If your app allows users to sign out, you can use the following method to reset the device to its initial logged-out state. After this, any subsequent events will be tied to the anonymous visitor ID.

Optimove.shared.signOutUser()
Optimove.getInstance().signOutUser();
Optimove.signOutUser();
Optimove.signOutUser();
Optimove.signOutUser();
Optimove.Shared.SignOutUser();

Tracking custom events

Once you and the Optimove Product Integration Team have reviewed your use cases, you will define the custom events together. The Product Integration Team will configure your particular events and parameters within your Optimove site, while you will be responsible for passing the event and parameter information to Optimove.

Key guidelines for custom events

  • Custom events must be first configured by Optimove Product Integration team before implementation
  • Events and parameters must be in lowercase and use snake_case naming convention only
    • snake_case definition: Separate each word with one underscore character (_) and no spaces. (e.g. checkout_completed)
  • The parameter types available for use in event reporting functions are:
    • String: A series of alphanumeric characters of up to 255 characters in length, using any encoding.
    • Number: Any numeric value, whether an integer or decimal.
    • Boolean: Is either true or false values, not a string.
  • Optimove supports up to 50 parameters per custom event

Reporting custom events

There are two methods for reporting a custom event through the Optimove SDK reportEvent() function: simple and complex. You can see both methods detailed below.

Reporting Simple Events

Optimove.shared.reportEvent(
    name: "signup", // the exact event name configured by the Product Integration Team (PIT)
    parameters: [   // (optional)  dictionary of parameter names and values, also configured by PIT
        "first_name": "John",
        "last_name": "Doe",
        "email": "[email protected]",
        "age": 42,
        "opt_in": false
    ]
)
Map<String, Object> params = new HashMap<>();
params.put("first_name", "John");
params.put("last_name", "Doe");
params.put("email", "[email protected]");
params.put("age", 42);
params.put("opt_in", false);

Optimove.getInstance().reportEvent(
  "signup", // the exact event name configured by the Product Integration Team (PIT)
  params // (optional) dictionary of parameter names and values, also configured by PIT
);
const params : Record<string, any> = {
  "first_name": 'John',
  "last_name": 'Doe',
  "email": '[email protected]',
  "age": '42',
  "opt_in": 'false',
}
    
Optimove.reportEvent(
  'signup', // the exact event name configured by the Product Integration Team (PIT)
  params // (optional) dictionary of parameter names and values, also configured by PIT
);
const params : Record<string, any> = {
  "first_name": 'John',
  "last_name": 'Doe',
  "email": '[email protected]',
  "age": '42',
  "opt_in": 'false',
}
    
Optimove.reportEvent(
  'signup', // the exact event name configured by the Product Integration Team (PIT)
  params // (optional) dictionary of parameter names and values, also configured by PIT
);
const params : Record<string, any> = {
  "first_name": 'John',
  "last_name": 'Doe',
  "email": '[email protected]',
  "age": '42',
  "opt_in": 'false',
}
    
Optimove.reportEvent(
  'signup', // the exact event name configured by the Product Integration Team (PIT)
  params // (optional) dictionary of parameter names and values, also configured by PIT
);
Dictionary<string, object> parameters = new Dictionary<string, object>()
{
    {"key", "value"},
};
Optimove.Shared.ReportEvent("eventName", parameters);

Reporting Complex Events

/*
 The Optimove SDK defines a protocol called OptimoveEvent with two properties:

 name: String – Declares the custom event's name
 parameters: [String: Any] – Specifies the custom event's parameters.

 Use this protocol to conform your complex event objects to the Optimove format 
 and pass it to the SDK via the Optimove.shared.reportEvent(_ event: OptimoveEvent) method:
*/

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        Optimove.shared.reportEvent(PlacedOrderEvent(self.cartItems))
    }
}

class PlacedOrderEvent: OptimoveEvent {

    private let cartItems: [CartItem]

    init(_ items: [CartItem]) {
        self.cartItems = items
    }

    var name: String {
        return "placed_order"
    }

    var parameters: [String : Any] {
        var params: [String: Any] = [:]
        var totalPrice = 0.0
        for i in 0..<self.cartItems.count {
            let item = self.cartItems[i]
            params["item_name_\(i)"] = item.name
            params["item_price_\(i)"] = item.price
            params["item_image_\(i)"] = item.image
            totalPrice += item.price
        }
        params["total_price"] = totalPrice
        return params
    }
}
/*
 * The Optimove SDK defines an interface called OptimoveEvent in two methods:
 *
 * String getName() – Declares the custom event's name
 * Map<String, Object> getParameters() – Specifies the custom event's parameters
 *
 * Use this interface to conform your complex event objects to the Optimove format
 * and pass it to the SDK via the Optimove.getInstance().reportEvent(OptimoveEvent optimoveEvent) method:
 */

public class MainActivity extends AppCompatActivity {

  public void reportPlacedOrderEvent(List<CartItem> cartItems) {
    Optimove.getInstance().reportEvent(new PlacedOrderEvent(cartItems));
  }
}

class PlacedOrderEvent implements OptimoveEvent {

  private List<CartItem> cartItems;

  public PlacedOrderEvent(List<CartItem> cartItems) {
    this.cartItems = cartItems;
  }

  @Override
  public String getName() {
    return "placed_order";
  }

  @Override
  public Map<String, Object> getParameters() {
    Map<String, Object> params = new HashMap<>();
    int totalPrice = 0;
    for (int i = 1; i < this.cartItems.size(); i++) {
      CartItem item = this.cartItems.get(i);
      params.put("item_name_" + i, item.name);
      params.put("item_price_" + i, item.price);
      params.put("item_image_" + i, item.image);
      totalPrice += item.price;
    }
    params.put("total_price", totalPrice);
    return params;
  }
}

Geolocation

Location Tracking

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

Once you have set up location updates from the OS (check sample below), you can send them to Optimove like so:

Optimove.shared.sendLocationUpdate(location: location);
Optimove.getInstance().sendLocationUpdate(location);
Feature isn't available in this SDK - contact your CSM.
import 'package:optimove_flutter/optimove_flutter.dart' as optimove;

// in the following example, location data was obtained using a location package https://pub.dev/packages/location
LocationData locationData = await location.getLocation();

Optimove.sendLocationUpdate(optimove.Location(longitude: locationData.longitude!, latitude: locationData.latitude!, time: locationData.time!));
Feature isn't available in this SDK - contact your CSM.
Feature isn't available in this SDK - contact your CSM.

Samples

There are multiple ways to achieve location tracking. The code samples below serve as an approximate example to get you started. They are not a working solution.

import UIKit
import CoreData
import OptimoveSDK
import CoreLocation

@main
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
   
    private var lm: CLLocationManager? = nil
    public func startLocationTracking() {
        let lm = CLLocationManager();
        lm.pausesLocationUpdatesAutomatically = false;
        lm.delegate = self
        self.lm = lm
        
        let status: CLAuthorizationStatus = lm.authorizationStatus
        if (CLAuthorizationStatus.authorizedAlways == status || CLAuthorizationStatus.authorizedWhenInUse == status) {
            self.startLocationMonitoring()
        }
        else{
            lm.requestAlwaysAuthorization()
        }
        
    }

    private func startLocationMonitoring(){
        self.lm!.startUpdatingLocation()
    }
    
    @available(iOS, deprecated: 14.0)
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        if (status == CLAuthorizationStatus.authorizedAlways) {
            self.startLocationMonitoring();
        }
    }
    
    @available(iOS 14.0, *)
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager){
        if (manager.authorizationStatus == CLAuthorizationStatus.authorizedAlways) {
            self.startLocationMonitoring();
        }
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        for loc in locations {
            Optimove.shared.sendLocationUpdate(location: loc);
        }
    }
}

/*
 *  Set up location tracking initialiser
 */

import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.tasks.Task;

public class LocationTrackingInitializer {
    public void startLocationTracking(Context context){
        this.mFusedLocationClient = LocationServices.getFusedLocationProviderClient(context);
        this.mLocationRequest = this.createLocationRequest();

        this.requestLocationUpdates(context);
    }

    private LocationRequest createLocationRequest() {
        LocationRequest request = new LocationRequest();

        request.setInterval(UPDATE_INTERVAL);
        request.setFastestInterval(FASTEST_UPDATE_INTERVAL);
        request.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        request.setMaxWaitTime(MAX_WAIT_TIME);
        request.setSmallestDisplacement(SMALLEST_DISPLACEMENT_FOR_LOCATION_UPDATE);

        return request;
    }

    private void requestLocationUpdates(Context context) {
        try {
            this.mFusedLocationClient.requestLocationUpdates(this.mLocationRequest, this.getPendingIntent(context));
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }

    private PendingIntent getPendingIntent(Context context) {
        Intent intent = new Intent(context, LocationBroadcastReceiver.class);
        intent.setAction(LocationBroadcastReceiver.ACTION_PROCESS_LOCATION_UPDATE);

        int flags = PendingIntent.FLAG_UPDATE_CURRENT;
        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            flags |= PendingIntent.FLAG_IMMUTABLE;
        }

        return PendingIntent.getBroadcast(context, 0, intent, flags);
    }
}


/*
 * Receive location updates and send them to Optimove
 */

import android.location.Location;
import com.google.android.gms.location.LocationResult;

public class LocationBroadcastReceiver extends BroadcastReceiver {

    static final String ACTION_PROCESS_LOCATION_UPDATE = "com.kumulos.companion.LOCATION_UPDATE";

    @Override
    public void onReceive(Context context, Intent intent) {
        if (null == intent) {
            return;
        }

        final String action = intent.getAction();
        if (!ACTION_PROCESS_LOCATION_UPDATE.equals(action)) {
            return;
        }

        LocationResult result = LocationResult.extractResult(intent);
        if (null == result) {
            return;
        }

        List<Location> locations = result.getLocations();
        Optimove instance = Optimove.getInstance();
        for (Location location : locations) {
            instance.sendLocationUpdate(location);
        }
    }
}

Beacon Detection

You can send to Optimove beacon proximity updates and use these to trigger a campaign when an install is in proximity to a beacon.

Eddystone Beacon Detection

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

/* Note: Eddystone beacon detection on iOS has limitations. For example, many of Core Bluetooth 
tasks are disabled when your app is in the background or in a suspended state. */
Optimove.shared.trackEddystoneBeaconProximity(hexNamespace: hexNamespace, hexInstance: hexInstance, distanceMeters: distanceMeters);
/* 
 * Note: Beacon monitoring on Android typically requires a device with BLE, and API level 18+. 
 * You can read more about detecting beacons in the Android Nearby reference:
 * https://developers.google.com/nearby/
 */
Optimove.getInstance().trackEddystoneBeaconProximity(hexNamespace, hexInstance, distanceMeters);
Feature isn't available in this SDK - contact your CSM.
/*
 * Android Note: Beacon monitoring on Android typically requires a device with BLE, and API level 18+. 
 * You can read more about detecting beacons in the Android Nearby reference: 
 * https://developers.google.com/nearby/
 * 
 * iOS Note: Eddystone beacon detection on iOS has limitations. For example, many of Core Bluetooth 
 * tasks are disabled when your app is in the background or in a suspended state.
 */
Optimove.trackEddystoneBeaconProximity(EddystoneBeaconProximity(hexNamespace: hexNamespace, hexInstance: hexInstance, distanceMetres: distanceMetres));
Feature isn't available in this SDK - contact your CSM.
Feature isn't available in this SDK - contact your CSM.

iBeacon Detection

You can also notify our services of proximity to a detected iBeacon as follows.

Optimove.shared.trackIBeaconProximity(beacon: CLBeacon);
Optimove.getInstance().trackIBeaconProximity(iBeaconUuid, iBeaconMajorId, iBeaconMinorId, iBeaconProximity);
Feature isn't available in this SDK - contact your CSM.
Feature isn't available in this SDK - contact your CSM.
Feature isn't available in this SDK - contact your CSM.
Feature isn't available in this SDK - contact your CSM.

Preference Center

To initialize Preference Center you need Preference Center SDK Credentials. You can find them in your Optimove dashboard Preference Center settings.

Note, for both delayed and standard initialization, Optimove credentials are also required for the Preference Center to work.

Enable Preference Center

Standard Initialization

Enable Preference Center like so:

Optimove.initialize(
    with: OptimoveConfigBuilder(
        optimoveCredentials: "",
        optimobileCredentials: ""
    )
    .enablePreferenceCenter(credentials: "")
    .build()
)
Optimove.initialize(this, new OptimoveConfig.Builder("optimoveCredentials", "optimobileCredentials")
            .enablePreferenceCenter("preferenceCenterCredentials")
            .build());

Delayed Initialization

Similar to here you can finish feature initialization later, for example, when your dynamically loaded credentials become available.

Add Preference Center to the list of desired features in the config builder. Note that Optimove needs to be included in the set of features for the Preference Center to work:

let config = OptimoveConfigBuilder(region: .US, features: [.optimove, .optimobile, .preferenceCenter]).build()
Optimove.initialize(with: config)

// time passes...

Optimove.setCredentials(
  optimoveCredentials: yourOptimoveCredentials,
  optimobileCredentials: yourOptimobileCredentials,
  preferenceCenterCredentials: yourPreferenceCenterCredentials
)
OptimoveConfig.FeatureSet featureSet = new OptimoveConfig.FeatureSet().withOptimove().withPreferenceCenter();
Optimove.initialize(this, new OptimoveConfig.Builder(OptimoveConfig.Region.DEV, featureSet).build());

// time passes...

Optimove.setCredentials(
  optimoveCredentials: yourOptimoveCredentials,
  optimobileCredentials: yourOptimobileCredentials,
  preferenceCenterCredentials: yourPreferenceCenterCredentials
)

Get Customer Preferences

To fetch a list of customer preferences, use the getPreferencesAsync method. Here’s an example implementation:

try OptimovePreferenceCenter.getInstance().getPreferencesAsync { result, preferences in
  switch result {
    case .success: ...
    case .errorUserNotSet: ...
    case .errorCredentialsNotSet: ...
    case .error: ...
  }
}
OptimovePreferenceCenter.getInstance().getPreferencesAsync((OptimovePreferenceCenter.ResultType result, Preferences preferences) -> {
            switch (result) {
                case ERROR_USER_NOT_SET:
                case ERROR_CREDNTIALS_NOT_SET:
                case ERROR:
                    ...
                    break;
                case SUCCESS: {
                    ...
                    break;
                }
            }
        });

Preference Properties

Configured Channels (the channels configured for the brand group)

preferences.configuredChannels // e.g. [.sms, .mobilePush]
preferences.getConfiguredChannels() // List<Channel> e.g. MOBILE_PUSH, SMS

Customer Preferences

preferences.customerPreferences // array of preferences, organised by topic
preferences.customerPreferences[0].id // topic id
preferences.customerPreferences[0].name // topic name
preferences.customerPreferences[0].description // topic description
preferences.customerPreferences[0].subscribedChannels // channels the customer is subscribed to, e.g. [.sms, .push]
List<Topic> customerPreferences = preferences.getCustomerPreferences()
customerPreferences[0].getId() // topic id
customerPreferences[0].getName() // topic name
customerPreferences[0].getDescription() // topic description
customerPreferences[0].getSubscribedChannels() // List<Channel> channels the customer is subscribed to, e.g. MOBILE_PUSH, SMS

Set Customer Preferences

For updating customer preferences use setCustomerPreferencesAsync:

try OptimovePreferenceCenter.getInstance().setCustomerPreferencesAsync(completion: { result in
  switch result {
    case .success: ...
    case .errorUserNotSet: ...
    case .errorCredentialsNotSet: ...
    case .error: ...
  }
}, updates: [OptimovePC.PreferenceUpdate])
OptimovePreferenceCenter
    .getInstance()
    .setCustomerPreferencesAsync((OptimovePreferenceCenter.ResultType setResult) -> {
       ...
    }, updates);

Complete Example

func fetchAndSetPreferences() {
    var preferences: OptimovePC.Preferences?

    // Fetch preferences asynchronously
    do {
        try OptimovePreferenceCenter.getInstance().getPreferencesAsync { result, fetchedPreferences in
            switch result {
            case .success:
                preferences = fetchedPreferences
                
                // Now that we have preferences, we can proceed with setting them
                if let preferences = preferences {
                    for topic in preferences.customerPreferences {
                        print("Topic ID: \(topic.id)")
                        print("Name: \(topic.name)")
                        print("Description: \(topic.description)")
                        print("Subscribed Channels: \(topic.subscribedChannels)")
                    }

                    // Prepare updates to customer preferences
                    let updates = [
                        OptimovePC.PreferenceUpdate(
                            topicId: preferences.customerPreferences[0].id,
                            // Note: You can only subscribe to channels that are configured
                            subscribedChannels: [.sms, .mail]
                        ),
                        OptimovePC.PreferenceUpdate(
                            topicId: preferences.customerPreferences[1].id,
                            subscribedChannels: []
                        )
                    ]

                    // Update preferences asynchronously
                    try? OptimovePreferenceCenter.getInstance().setCustomerPreferencesAsync(completion: { setResult in
                        switch setResult {
                        case .success:
                            print("Success: Preferences have been updated successfully.")
                        case .errorUserNotSet:
                            print("Error: Unable to update preferences. Preference Center requires an associated user. Please set a customer id.")
                        case .errorCredentialsNotSet:
                            print("Error: Unable to update preferences due to missing credentials. Please ensure your credentials are configured correctly.")
                        case .error:
                            print("Error: An issue occurred while updating preferences.")
                        }
                    }, updates: updates)
                }

            case .errorUserNotSet:
                print("Error: No user is associated. The Preference Center requires an associated user. Please set a customer id.")
            case .errorCredentialsNotSet:
                print("Error: Credentials are not set. Please check your credentials configuration.")
            case .error:
                print("Error: An issue occurred while fetching preferences.")
            }
        }
    } catch {
        print("Exception: \(error.localizedDescription). Please check the stack trace for more details.")
    }
}
AtomicReference<Preferences> preferences = new AtomicReference<>();

OptimovePreferenceCenter.getInstance().getPreferencesAsync((OptimovePreferenceCenter.ResultType result, Preferences fetchPreferences) -> {
    switch (result) {
        case ERROR_USER_NOT_SET:
        case ERROR:
        case ERROR_CREDENTIALS_NOT_SET:
        // Handle errors
            break;
        case SUCCESS: {
            preferences.set(fetchPreferences);
            break;
        }
        default:
            // ...
        }
    });

if (preferences.get() == null) {
    return;
}

List<Channel> configuredChannels = preferences.get().getConfiguredChannels();
List<Topic> topics = preferences.get().getCustomerPreferences();

List<PreferenceUpdate> updates = new ArrayList<>();
for (int i = 0; i < topics.size(); i++) {
    // Note, you can only subscribe to channels that are configured
    updates.add(new PreferenceUpdate(topics.get(i).getId(), configuredChannels.subList(0, 1)));
}

OptimovePreferenceCenter.getInstance().setCustomerPreferencesAsync((OptimovePreferenceCenter.ResultType setResult) -> {
    // Handle result;
}, updates);

Push Notifications

If you haven't already, set up push notifications by following the steps in our Getting Started guide.

Allow push notifications

When you consider it appropriate, you need to request permission from the user to send them push notifications. You can potentially use an in-app message to prompt the user to accept push at a later time.

Whilst you can handle this yourself, Optimove provides a convenience method. When the user accepts, the Optimove SDK will store the push token.

Optimove.shared.pushRequestDeviceToken()  
Optimove.getInstance().pushRequestDeviceToken(); // Starting from Android 13, this method will open the notification permission request prompt 
Optimove.pushRequestDeviceToken();
Optimove.pushRequestDeviceToken();
Optimove.pushRequestDeviceToken();
Optimove.Shared.PushRegister();

Make sure to provide your OptiMobile credentials during the initialization, as they are necessary for Push Messaging.

Handle push notifications

Android

To handle push notifications in the Android SDK, you can override methods in the PushBroadcastReceiver class. By default, the receiver shows notifications and opens the main activity on tap, but you can customize this behavior.

For customization, subclass PushBroadcastReceiver and override methods like onPushReceived or getPushOpenActivityIntent. Remember to update your AndroidManifest.xml to use your custom receiver.

Refer to the Android Customization Section below for detailed examples and additional methods you can override.

iOS

To correctly handle push notifications in the iOS SDK, ensure you have set up a Notification Service Extension and added the App Groups capability to both your app and the Notification Service Extension targets. This setup is crucial for push delivery tracking, dismissed tracking, and badge handling.

When a user interacts with a push message, the pushOpenedHandlerBlock is called, where you can define custom actions based on the notification data. For more details, refer to the iOS Customization Section below.

Cross-platform

Optimove.setPushReceivedHandler((notification) {
    // Called when a push is received with your app in the foreground
});

Optimove.setPushOpenedHandler((notification) => {
    // Called when a user taps on a push notification or its action buttons
});
Optimove.setPushOpenedAndDeeplinkHandlers((push) {
    // Called when a user taps on a push notification or its action buttons
}, (outcome) {
    // Called when the app is opened with a deeplink
});

Optimove.setPushReceivedHandler((push) {
    // Called when a push is received with your app in the foreground
});

Optimove.setPushOpenedHandler((push) {
    // Called when a user taps on a push notification or its action buttons
});
Optimove.setPushReceivedHandler((push: PushNotification) => {
    // Called when a push is received with your app in the foreground
});

Optimove.setPushOpenedHandler((push: PushNotification) => {
    // Called when a user taps on a push notification or its action buttons
});
Optimove.Shared.SetPushReceivedHandler((PushMessage push) => {
    // Called when a push is received with your app in the foreground
});

Optimove.Shared.SetPushOpenedHandler((PushMessage push) => {
    // Called when a user taps on a push notification or its action buttons
});

Opt-outs

If at a later stage the user wishes to opt out from push notifications without blocking them via the Notification Center Optimove provides a convenience method. This will mark the token as soft-deleted in Optimove to allow for easy re-enabling later.

Optimove.shared.pushUnregister()
Optimove.getInstance().pushUnregister();
Optimove.pushUnregister();
Optimove.pushUnregister();
Optimove.pushUnregister();
Optimove.Shared.PushUnregister();

iOS Customization

Supporting Pictures and Action Buttons in Notifications

When sending a push notification you can attach a picture or action buttons to it. They will show on iOS 10+ devices.

The notification will expand upon swiping the notification on devices supporting 3D Touch. In order to enable this functionality you need to add a Notification Service Extension to your application.

In order to enable Optimove to track the push notifications, you'll need to add a Notification Service Extension to your project for each App-target. This app extension creates a process that handles incoming Push Notifications manipulation. To add this extension to your app please follow the steps below:

  1. Select File > New > Target on XCode

  2. Select the Notification Service Extension target from the iOS > Application section

  3. Click Next

  4. Specify a name for your extension

  5. Click Finish

  6. In your Podfile add a new target matching the extension's name

  7. Locate the Notification Service Extension's target declaration

  8. Add the OptimoveNotificationServiceExtension SDK to the target's dependencies list

If using CocoaPods, add the following to your Podfile and run pod install.

Podfile code snippet

# The Optimove SDK supports iOS version 10 and higher
platform :ios, '10.0'

target 'My Application' do # Your app target
  use_frameworks!
  pod 'OptimoveSDK', '~> 5.0' # We've added this dependency in a previous step
end

target 'My Notification Extension' do # Your new extension target
  use_frameworks!
  pod 'OptimoveNotificationServiceExtension', '~> 5.0'
end

Then replace the contents of NotificationService.swift with the following lines:

import UserNotifications
import OptimoveNotificationServiceExtension

class NotificationService: UNNotificationServiceExtension {
    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        OptimoveNotificationService.didReceive(request, withContentHandler: contentHandler)
    }
}

Optimove's helper function automatically adds picture attachments and buttons to the notification content. You can modify the content before calling didReceive or replace the implementation with your own.

Supporting push delivery tracking, dismissed tracking and badges

For push delivery tracking, dismissed tracking and badges to work correctly you need to

  1. Set up a Notification Service Extension as described above
  2. Add the App Groups capability to your App and Notification Service Extension targets
  3. Set group to group.{your.app.target.bundle.identifier}.optimove for both targets

Note that due to iOS limitations badge is not set when app is in the foreground

Handling notification action buttons

When a user interacts with your push message the pushOpenedHandlerBlock will be called, in this block you can provide further behavior to handle custom actions.

let cfg = OptimoveConfigBuilder(optimoveCredentials: "<YOUR_OPTIMOVE_CREDENTIALS>", optimobileCredentials: "<YOUR_OPTIMOVE_MOBILE_CREDENTIALS>")
    .setPushOpenedHandler(pushOpenedHandlerBlock: { (notification : PushNotification) -> Void in
        //- Inspect notification data and do work.
        if let action = notification.actionIdentifier {
            print("User pressed an action button.")
            print(action)
            print(notification.data)
        } else {
            print("Just an open event.")
        }
    })
    .build()

Optimove.initialize(config: cfg)

Handling Background Data Pushes

When you send a background data push with Optimove, 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:

// iOS9 handler for push notifications
// iOS9+10 handler for background data pushes (content-available)
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        // userInfo["aps"]["content-available"] will be set to 1
        // userInfo["custom"]["a"] will contain any additional data sent with the push

        completionHandler(UIBackgroundFetchResult.noData)
    }

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.

Android Customization

Overriding PushBroadcastReceiver

Default Push Behavior

By default, the Optimove PushBroadcastReceiver can show a notification on the notification area of the device, or as a heads-up notification when a content push is received with the appropriate setting selected.

Tapping this notification will open the main launcher activity of your application and track the push open conversion for you.

Your main activity will receive the push contents in its options bundle under the PushMessage.EXTRAS_KEY.

Customizing Push Behavior

To customize the behavior of the SDK when a push is received or its notification is tapped, we suggest subclassing the PushBroadcastReceiver and overriding its base methods depending on what you want to customize.

Example extension class:

package com.example;

import com.optimove.android.optimobile.PushBroadcastReceiver;

public class MyPushReceiver extends PushBroadcastReceiver {

}

Make sure to change the AndroidManifest.xml receiver:

<receiver android:name="com.example.MyPushReceiver" android:exported="false">
    <intent-filter>
        <action android:name="com.optimove.push.RECEIVED" />
        <action android:name="com.optimove.push.OPENED" />
        <action android:name="com.optimove.push.DISMISSED" />
        <action android:name="com.optimove.push.BUTTON_CLICKED" />
    </intent-filter>
</receiver>

You can override the following methods:

  • getPushOpenActivityIntent -- to change the activity launched when notification tapped
  • getNotificationBuilder -- to customize notification (*)
  • onBackgroundPush -- to perform work when a background data push is received
  • onPushReceived -- overriding all behaviours (* not adviceable)

Note: (*) overriding these methods may interfer with the correct functioning of the SDK, for example correct tracking of open events. Please, refer to the relevant section for details

Changing the Launched Activity

To change which activity will be launched when the user taps a notification, you can override the PushBroadcastReceiver#getPushOpenActivityIntent(Context, PushMessage).

package com.example;

import android.content.Context;
import android.content.Intent;

import com.optimove.android.optimobile.PushBroadcastReceiver;
import com.optimove.android.optimobile.PushMessage;

public class MyPushReceiver extends PushBroadcastReceiver {

    @Override
    protected Intent getPushOpenActivityIntent(Context context, PushMessage pushMessage) {
        // TODO implement your own logic here
        return super.getPushOpenActivityIntent(context, pushMessage);
    }
}

The PushMessage model will not be added to the Intent by default, it is up to you to add it as an extra if desired:

Intent launchIntent = new Intent(context, MyActivity.class);
launchIntent.putExtra(PushMessage.EXTRAS_KEY, pushMessage);

You can return null to track the push conversion and do nothing when the notification is tapped.

If the Intent returned does not describe an Activity, it will be ignored

Customizing the Notification

To customize the notification shown to the user for content pushes, you can override PushBroadcastReceiver#getNotificationBuilder(Context, PushMessage).

package com.example;

import android.app.Notification;
import android.content.Context;

import com.optimove.android.optimobile.PushBroadcastReceiver;
import com.optimove.android.optimobile.PushMessage;

public class MyPushReceiver extends PushBroadcastReceiver {
    @Override
    protected @Nullable Notification.Builder getNotificationBuilder(Context context, PushMessage pushMessage) {
        // get builder with Optimove intents set up
        Notification.Builder builder = super.getNotificationBuilder(context, pushMessage);
        // TODO customize the notification
        builder.setContentTitle("custom: " + pushMessage.getTitle());

        return builder;
    }
}

If you want to handle the open/dismissal with the broadcast receiver, and decide to use your new, custom Notification.Builder (not advisable), be sure to set up the intents of the notification correctly. Refer to PushBroadcastReceiver source. This will ensure that the notification conversion (opens, dismissed, delivered) is tracked in Optimove, that Optimove push action handler is fired and prevent possible Android 12 trampoline issues.

If you want to do something else, you can manually track push open conversion using Optimove.getInstance().pushTrackOpen(int) and track dismissed event using Optimove.getInstance().pushTrackDismissed(int). Additionally, you would have to add deep link extras for in-app message deep links to keep working.

Optimove.getInstance().pushTrackOpen(pushMessage.getId());
Optimove.getInstance().pushTrackDismissed(pushMessage.getId());
//call in the scope of MyPushReceiver
addDeepLinkExtras(pushMessage, launchIntent);

Launching a Service for Background Data Pushes

To perform work when a background data push is received, you can override PushBroadcastReceiver#onBackgroundPush.

package com.example;

import android.content.Context;
import android.content.Intent;

import com.optimove.android.optimobile.PushBroadcastReceiver;
import com.optimove.android.optimobile.PushMessage;

public class MyPushReceiver extends PushBroadcastReceiver {

    @Override
    protected void onBackgroundPush(Context context, PushMessage pushMessage) {
        WorkManager workManager = WorkManager.getInstance(context);
        workManager.enqueue(new OneTimeWorkRequest.Builder(MyWorker.class).build());
    }

}

Overriding All Behaviors

If you want to completely replace the logic for handling pushes, you can override PushBroadcastReceiver#onPushReceived(Context, PushMessage).

Bear in mind you will be responsible for all aspects of the push process such as showing a notification to the user, tracking an open conversion using Optimove.getInstance().pushTrackOpen(int) and dismissed events using Optimove.getInstance().pushTrackDismissed(int), or launching any activities or services.

In addition, you may need to implement behaviors for:

  • Delivery tracking: pushTrackDelivered(pushMessage)
  • In-App message sync: maybeTriggerInAppSync(pushMessage)
  • Clearing notifications for in-app messages requires presenting with the Optimove tag like so: notificationManager.notify(OPTIMOVE_NOTIFICATION_TAG, this.getNotificationId(pushMessage) , notification)

Using Your Own FirebaseMessagingService with Optimove

If you already consume FCM push notifications with your own FirebaseMessagingService but you want to also enjoy the benefits of the Optimove push service, you can use the SDK's helper methods in your own implementation. For example:

public class MyAppFirebaseMessagingService extends com.google.firebase.messaging.FirebaseMessagingService {

    @Override
    public void onNewToken(String token) {
        // Handle token for your purposes
        // ...
        // Also pass token to Optimove for registration
        Optimove.pushTokenStore(token);
    }

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        // Handle message as you wish or forward this message to Optimove
        boolean wasHandledByOptimove = com.optimove.android.optimobile.FirebaseMessageHandler.onMessageReceived(this, remoteMessage);
        // Handle the message in case false is returned
    }
}

Note, that all issues with tracking conversion events (opens, dismissed, delivered) mentioned in overriding PushBroadcastReceiver section may also apply here.

When you are doing this in a cross-platform app, pay extra attention

Notes for cross-platform apps

Make sure you're using the correct FirebaseMessagingService entry in the manifest to avoid conflicts. For example, in Cordova, when the Android plugin is added, it automatically modifies the manifest to include FirebaseMessagingService. However, if you later edit the manifest in the exported project and re-add the plugin, it may duplicate entries.

Handling Push Action Buttons

Push messages allow you to hand-off to native application screens via deep-linking push action buttons. When tapped, these buttons pass control to the defined push action handler.

If you want to handle deep-links as part of a Push message you can create a class that implements the PushActionHandlerInterface and assign it during SDK initialization.

Optimove.getInstance().setPushActionHandler(new MyPushActionHandler());

A stub implementation of the handler could be as follows:

public class MyPushActionHandler implements PushActionHandlerInterface {
    public void handle(Context context, PushMessage pushMessage, String actionId){
       //- actionId is the button id you set when creating the notification
       //- Note, that when action button is clicked your app's activity is not launched -- you have to do it yourself in this handler.
    }
}

Changing the Push Icon

To change the icon shown in the status bar on Android, you can configure Optimove with a drawable at initialization time:

OptimoveConfig config = new OptimoveConfig.Builder(
    "<YOUR_OPTIMOVE_CREDENTIALS>","<YOUR_OPTIMOVE_MOBILE_CREDENTIALS>"
    ).setPushSmallIconId(R.id.my_push_small_icon)
    .build()

Optimove.initialize(this, config);

Make sure to comply with the status bar icon guidelines so the icon renders correctly on all devices. In particular, using non-alpha channel icons can lead to issues on some devices. For help preparing assets, we suggest checking out the Android Asset Studio

URL Pushes

Push notifications sent to open a URL will, by default, track the push open if the user taps the notification, and then open the default web browser.

In-App Messages

📘

To unlock this capability, you will need to have added In-App Messaging to your Optimove package. If you can’t see this feature in your Optimove instance, contact your CSM to find out more.

Enabling

To integrate In-App messaging you only need to configure the SDK to enable the feature during initialization.

let cfg = OptimoveConfigBuilder(optimoveCredentials: "<YOUR_OPTIMOVE_CREDENTIALS>", optimobileCredentials: "<YOUR_OPTIMOVE_MOBILE_CREDENTIALS>")
    .enableInAppMessaging(inAppConsentStrategy: .autoEnroll)
    .build()
Optimove.initialize(config: cfg)
OptimoveConfig config = new OptimoveConfig.Builder(
    "<YOUR_OPTIMOVE_CREDENTIALS>","<YOUR_OPTIMOVE_MOBILE_CREDENTIALS>"
    ).enableInAppMessaging(OptimoveConfig.InAppConsentStrategy.AUTO_ENROLL)
    .build()

Optimove.initialize(this, config);
OptimoveReactNativeInitializer.initializeOptimove(
  OptimoveReactNativeConfig.newInstance()
    .optimoveCredentials("<YOUR OPTIMOVE CREDENTIALS>")
    .optimobileCredentials("<YOUR OPTIMOBILE CREDENTIALS>")
    .enableInAppWithConsentStrategy(OptimoveConfig.InAppConsentStrategy.AUTO_ENROLL)
    .build(), getApplicationContext());
// Add inAppConsentStrategy to your optimove.json along previously added credentials:
{
  "optimoveCredentials": "YOUR_OPTIMOVE_CREDENTIALS",
  "optimobileCredentials": "YOUR_OPTIMOVE_MOBILE_CREDENTIALS",
  "inAppConsentStrategy": "auto-enroll", //in-app-disabled|auto-enroll|explicit-by-user
  "enableDeferredDeepLinking": false
}
// Add inAppConsentStrategy to your optimove.json along previously added credentials:
{
  "optimoveCredentials": "YOUR_OPTIMOVE_CREDENTIALS",
  "optimobileCredentials": "YOUR_OPTIMOVE_MOBILE_CREDENTIALS",
  "inAppConsentStrategy": "auto-enroll", //in-app-disabled|auto-enroll|explicit-by-user
  "enableDeferredDeepLinking": false
}
// Add inAppConsentStrategy to your optimove.json along previously added credentials:
{
  ...
  "inAppConsentStrategy": "auto-enroll", //in-app-disabled|auto-enroll|explicit-by-user
}

This configuration will automatically enroll all your app users to be reachable by In-App messages, the SDK will automatically present and track opens.

Managing Consent

If you would like your users to opt-in to receive In-App messages you can configure the SDK during initialization to make opt-in explicit by setting the strategy, then calling the SDK helper to manage their consent.

let cfg = OptimoveConfigBuilder(optimoveCredentials: "<YOUR_OPTIMOVE_CREDENTIALS>", optimobileCredentials: "<YOUR_OPTIMOVE_MOBILE_CREDENTIALS>")
    .enableInAppMessaging(inAppConsentStrategy: InAppConsentStrategy.explicitByUser)
    .build()
Optimove.initialize(config: cfg)

// Call this method to update consent based on user preferences / settings screen etc.
OptimoveInApp.getInstance().updateConsentForUser(true);
// Set the strategy to require explicit user consent
OptimoveConfig config = new OptimoveConfig.Builder(
    "<YOUR_OPTIMOVE_CREDENTIALS>","<YOUR_OPTIMOVE_MOBILE_CREDENTIALS>"
    ).enableInAppMessaging(OptimoveConfig.InAppConsentStrategy.EXPLICIT_BY_USER)
    .build()

Optimove.initialize(this, config);

// Call this method to update consent based on user preferences / settings screen etc.
OptimoveInApp.getInstance().updateConsentForUser(true);
OptimoveReactNativeInitializer.initializeOptimove(
  OptimoveReactNativeConfig.newInstance()
    .optimoveCredentials("<YOUR OPTIMOVE CREDENTIALS>")
    .optimobileCredentials("<YOUR OPTIMOBILE CREDENTIALS>")
    .enableInAppWithConsentStrategy(OptimoveConfig.InAppConsentStrategy.EXPLICIT_BY_USER)
    .build(), getApplicationContext());

// You can control the consent by calling the following method:
Optimove.inAppUpdateConsent(boolean)
// optimove.json
{
  "optimoveCredentials": "YOUR_OPTIMOVE_CREDENTIALS",
  "optimobileCredentials": "YOUR_OPTIMOVE_MOBILE_CREDENTIALS",
  "inAppConsentStrategy": "explicit-by-user",
  "enableDeferredDeepLinking": false
}

// You can control the consent by calling the following method:
Optimove.inAppUpdateConsent(boolean)
// optimove.json
{
  "optimoveCredentials": "YOUR_OPTIMOVE_CREDENTIALS",
  "optimobileCredentials": "YOUR_OPTIMOVE_MOBILE_CREDENTIALS",
  "inAppConsentStrategy": "explicit-by-user",
  "enableDeferredDeepLinking": false
}

// You can control the consent by calling the following method:
Optimove.inAppUpdateConsent(boolean)
// Add `inAppConsentStrategy` to your `optimove.json` along previously added credentials:
{
  ...
  "inAppConsentStrategy": "explicit-by-user",
}

// You can control the consent by calling the following method:
Optimove.Shared.InAppUpdateConsent(true);

Deep-linking

In-App messages allow you to hand-off to native application screens via deep-linking action buttons. When tapped, these buttons pass control to the defined deep-link handler, including their defined data payload (configured in the In-App message composer for the action button).

/* If you want to handle deep-links with custom data payloads as part of an In-App message you 
can add a handler block to your configuration options during SDK initialization. */

let cfg = OptimoveConfigBuilder(optimoveCredentials: "<YOUR_OPTIMOVE_CREDENTIALS>", optimobileCredentials: "<YOUR_OPTIMOVE_MOBILE_CREDENTIALS>")
    .enableInAppMessaging(inAppConsentStrategy: InAppConsentStrategy.autoEnroll)
    .setInAppDeepLinkHandler(inAppDeepLinkHandlerBlock: { buttonPress in
        let deepLink = buttonPress.deepLinkData
        let messageData = buttonPress.messageData

        // TODO: Inspect the deep link & message data to perform relevant action
    })
    .build()

Optimove.initialize(config: cfg)
/* If you want to handle deep-links with custom data payloads as part of an In-App message
you can create a class that implements the InAppDeepLinkHandlerInterface and add it to your
configuration options during SDK initialization: */

OptimoveInApp.getInstance().setDeepLinkHandler((context, buttonPress) -> {
    JSONObject deepLink = buttonPress.getDeepLinkData();
    JSONObject messageData = buttonPress.getMessageData();

    // TODO: Inspect the deep link & message data to perform relevant action
});
// The following sample code shows how to use Optimove to control the in-app buttons:
Optimove.setInAppDeeplinkHandler((inAppButtonPress) {
    // Called when a user taps on an in-app button
}
// The following sample code shows how to use Optimove to control the in-app buttons:
Optimove.setInAppDeeplinkHandler(inAppButtonPress) {
    // Called when a user taps on an in-app button
}
// The following sample code shows how to use Optimove to control the in-app buttons:
Optimove.setInAppDeeplinkHandler((inAppButtonPress: InAppButtonPress) => {
    // Called when a user taps on an in-app button
});
// The following sample code shows how to use Optimove to control the in-app buttons:
Optimove.Shared.SetInAppDeeplinkHandler((InAppButtonPress buttonPress) => {
    // Called when a user taps on an in-app button
});

In-App Inbox

In-app messages can optionally be persisted in a user-level inbox for later retrieval. This allows you to build features such as loyalty rewards or expiring coupons into your app. Regardless of whether they are stored in the inbox, the maximum amount of in-apps stored on a device is 50 (the oldest messages exceeding this limit will be evicted).

Retrieve messages

To retrieve a list of messages from the user's inbox and present the first in the list:

let inboxItems = OptimoveInApp.getInboxItems()
if(inboxItems.count > 0)
{
    let result = OptimoveInApp.presentInboxMessage(item: inboxItems[0])    
}
List<InAppInboxItem> items = OptimoveInApp.getInstance().getInboxItems();
if (items.size() != 0) {
    OptimoveInApp.InboxMessagePresentationResult result = OptimoveInApp.getInstance().presentInboxMessage(items.get(0));
}
var items = await Optimove.inAppGetInboxItems();
Optimove.inAppPresentInboxMessage(items.first);
var items = await Optimove.getInboxItems();
Optimove.presentInboxMessage(items.first);
Optimove.inAppGetInboxItems().then((items:InAppInboxItem[]) => {
   Optimove.inAppPresentInboxMessage(items[0]);
}, error);
List<InAppInboxItem> items = Optimove.Shared.InAppGetInboxItems();
Optimove.Shared.InAppPresentInboxMessage(items[0]);

Note that if in-app message display is paused then inbox items will not be presented and will instead return the status .paused to allow your app to handle the condition.

Mark as read

To mark a single or all inbox messages as read:

//single
let inboxItems = OptimoveInApp.getInboxItems()
if(inboxItems.count > 0)
{
    OptimoveInApp.markAsRead(item: inboxItems[0])
}

//all
OptimoveInApp.markAllInboxItemsAsRead()
//single
List<InAppInboxItem> items = OptimoveInApp.getInstance().getInboxItems(context);

if (items.size() != 0) {
    OptimoveInApp.getInstance().markAsRead(context, items.get(0));
}

//all
OptimoveInApp.getInstance().markAllInboxItemsAsRead(context);
var items = await Optimove.inAppGetInboxItems();

//single
Optimove.inAppMarkAsRead(items.first);
//all
Optimove.inAppMarkAllInboxItemsAsRead();
var items = await Optimove.inAppGetInboxItems();

//single
Optimove.inAppMarkAsRead(items.first);
//all
Optimove.inAppMarkAllInboxItemsAsRead();
//single
Optimove.inAppGetInboxItems().then((items:InAppInboxItem[]) => {
   Optimove.markAsRead(items[0]);
}, error);

//all
Optimove.markAllInboxItemsAsRead();
//single
int targetId = 0(for example); 
List<InAppInboxItem> items = Optimove.Shared.InAppGetInboxItems();
foreach (var item in items) {
  if (item.Id == targetId){
    bool result = Optimove.Shared.InAppMarkAsRead(item);
    break;
  }
}

//all
Optimove.Shared.MarkAllInboxItemsAsRead();

Delete message

You can also delete an in-app message from inbox:

let inboxItems = OptimoveInApp.getInboxItems()
if(inboxItems.count > 0)
{
    OptimoveInApp.deleteMessageFromInbox(item: inboxItems[0]);
}
List<InAppInboxItem> items = OptimoveInApp.getInstance().getInboxItems(context);
if (items.size() != 0) {
    OptimoveInApp.getInstance().deleteMessageFromInbox(context, items.get(0));
}
var items = await Optimove.inAppGetInboxItems();
Optimove.inAppDeleteMessageFromInbox(items.first);
var items = await Optimove.inAppGetInboxItems();
Optimove.inAppDeleteMessageFromInbox(items.first);
Optimove.inAppGetInboxItems().then((items:InAppInboxItem[]) => {
   Optimove.inAppDeleteMessageFromInbox(items[0]);
}, error);
int targetId = 0 (for example);
List<InAppInboxItem> items = Optimove.Shared.InAppGetInboxItems();
foreach (var item in items) {
  if (item.Id == targetId){
    bool result = Optimove.Shared.InAppDeleteMessageFromInbox(item);
    break;
  }
}

Inbox updated handler

In order to be notified when inbox changes you may set up a handler. The handler fires on the main thread when one of the following happens to an in-app with inbox:

  • message fetched from server
  • message opened
  • message marked as read
  • message deleted
  • message evicted (expires or limit of stored messages exceeded)

You can use it as follows:

OptimoveInApp.setOnInboxUpdated(inboxUpdatedHandlerBlock: {() -> Void in
    let inboxItems = OptimoveInApp.getInboxItems()

    //refresh your inbox
});

// Note, you can do OptimoveInApp.setOnInboxUpdated(inboxUpdatedHandlerBlock: nil) when you stop being interested in inbox updates.
OptimoveInApp.getInstance().setOnInboxUpdated(() -> {
    List<InAppInboxItem> items = OptimoveInApp.getInstance().getInboxItems(context);

    //refresh your inbox
});

// Note, you can do OptimoveInApp.getInstance().setOnInboxUpdated(null) when you stop being interested in inbox updates.
var items = await Optimove.inAppGetInboxItems();
Optimove.inAppDeleteMessageFromInbox(items.first);
Optimove.setOnInboxUpdatedHandler(() {
      _loadState();
});
Optimove.setOnInboxUpdatedHandler(() => {
   //
});
Optimove.Shared.SetOnInboxUpdatedHandler(() => {
   //handle
});

Get inbox summary

You can retrieve an inbox summary as follows:

 OptimoveInApp.getInboxSummaryAsync(inboxSummaryBlock: {(summary:InAppInboxSummary?) -> Void in
    if let inboxSummary = summary {
        print("total: \(inboxSummary.totalCount) unread: \(inboxSummary.unreadCount)")
    }
});
OptimoveInApp.getInstance().getInboxSummaryAsync(context, (InAppInboxSummaryInfo summary) -> {
    if (summary != null){
        summary.getTotalCount();
        summary.getUnreadCount();
    }
});
var summary = await Optimove.inAppGetInboxSummary();
var summary = await Optimove.inAppGetInboxSummary();
Optimove.inAppGetInboxSummary().then((inboxSummary: InAppInboxSummary) => {
  //
}, error);
Optimove.Shared.GetInboxSummaryAsync((InAppInboxSummary summary) => {
            //use summary
        });

The method runs asynchronously and calls back on the UI thread.

Get inbox item's image URL

Each inbox item may have an image associated with it. getImageUrl returns a URL to the image of specified width or null if there is no image.

let inboxItems = OptimoveInApp.getInboxItems()

if(inboxItems.count > 0)
{
    // Default width is 300px
    let url = inboxItems[0].getImageUrl();

    // Get URL to a 200px wide image
    let url = items[0].getImageUrl(width: 200);    
}
List<InAppInboxItem> items = OptimoveInApp.getInstance().getInboxItems(context);

if (items.size() != 0) 
{
    // Default width is 300px
    URL u = items.get(0).getImageUrl();

    // Get URL to a 200px wide image
    URL u = items.get(0).getImageUrl(200);
}
var items = await Optimove.inAppGetInboxItems();
items[0].imageUrl;
var items = await Optimove.inAppGetInboxItems();
String url = items.first["imageUrl"];
Optimove.inAppGetInboxItems().then((items:InAppInboxItem[]) => {
   items[0]["imageUrl"];
}, error);
List<InAppInboxItem> items = Optimove.Shared.InAppGetInboxItems();
Optimove.Shared.InAppPresentInboxMessage(items[0].ImageUrl);

Controlling Display

Required minimum SDK Version

SDK version 5.3.0+
SDK version 7.2.0+

It is possible to temporarily pause & resume in-app message display. This can be useful to prevent in-apps showing on particular screens in your app or interrupting full-screen experiences.

There are two in-app display modes defined:

  • automatic (the default): in-app messages are shown as soon as eligible
  • paused: in-app messages may be queued for display, but not shown to the user

If display is paused, and then set to automatic, display is considered 'resumed' and any messages queued whilst the display was paused will be shown to the user as normal.

Setting the display mode

You can use the following APIs to manipulate the in-app display mode:

OptimoveInApp.setDisplayMode(mode: .paused)
OptimoveInApp.setDisplayMode(mode: .automatic)
OptimoveInApp.getDisplayMode()
OptimoveInApp.getInstance().setDisplayMode(OptimoveConfig.InAppDisplayMode.PAUSED);
OptimoveInApp.getInstance().setDisplayMode(OptimoveConfig.InAppDisplayMode.AUTOMATIC);
OptimoveConfig.InAppDisplayMode mode = OptimoveInApp.getInstance().getDisplayMode();

Changing display mode whilst an in-app message is showing will take effect once the current message is closed.

Attempting to present an inbox item whilst display is paused will return a status enum indicating that display is paused, and the item is not presented.

Setting a default display mode

If you don't want in-app messages to show whilst your app is loading, you can use the following configuration option to pause in-app display by default:

let cfg = OptimoveConfigBuilder(optimoveCredentials: "<YOUR_OPTIMOVE_CREDENTIALS>", optimobileCredentials: "<YOUR_OPTIMOVE_MOBILE_CREDENTIALS>")
    .enableInAppMessaging(inAppConsentStrategy: .autoEnroll, defaultDisplayMode: .paused)
    .build()
OptimoveConfig config = new OptimoveConfig.Builder(
    "<YOUR_OPTIMOVE_CREDENTIALS>","<YOUR_OPTIMOVE_MOBILE_CREDENTIALS>"
    ).enableInAppMessaging(OptimoveConfig.InAppConsentStrategy.AUTO_ENROLL, OptimoveConfig.InAppDisplayMode.PAUSED)
    .build()

Then, when you are ready to resume display, you can set the mode to automatic:

OptimoveInApp.setDisplayMode(mode: .automatic)
OptimoveInApp.getInstance().setDisplayMode(OptimoveConfig.InAppDisplayMode.AUTOMATIC);

Deferred Deep Linking

Deep linking allows users to reach app content by clicking a link. This can be achieved even if the app is not installed.

📘

To unlock this capability, you will need to have added Deferred Deep Linking to your Optimove package. If you can’t see this feature in your Optimove instance, contact your CSM to find out more.

Setup in Optimove

Go to settings and select 'Configuration' under the 'Deferred Deep Linking' heading to start.

Click Edit Configurations

Enter the subdomain (of the lnk.click domain) that you want to use for your Deferred Deep Links.

1. Confirm the subdomain matches the domain you set in the Associated Domains capability 
in your Xcode project.

2. Toggle the switch next to iOS Config on. Enter the URL to your app in the App Store, 
the Bundle ID for your app and your Apple Developer Account Team ID (which can be found 
in the Apple Membership Section -> https://developer.apple.com/account/#/membership).
To use Deferred Deep Links with your Android app, toggle the switch next to Android Config on. 
Enter the URL to your app in the Play Store, the Package for your app and the colon separated, 
SHA256 signing certificate fingerprint(s).

Note that if you are publishing to Google Play and app signing by Google Play is enabled, 
you need to add two SHA256 fingerprints: one obtained from signing APK locally and one 
obtained from Google Play console. Fingerprints are necessary for deep linking into your app 
without showing a disambiguation dialog.
To use Deferred Deep Links with your Android app, toggle the switch next to Android Config on. 
Enter the URL to your app in the Play Store, the Package for your app and the colon separated, 
SHA256 signing certificate fingerprint(s).

Note that if you are publishing to Google Play and app signing by Google Play is enabled, 
you need to add two SHA256 fingerprints: one obtained from signing APK locally and one 
obtained from Google Play console. Fingerprints are necessary for deep linking into your app 
without showing a disambiguation dialog. 
To use Deferred Deep Links with your Android app, toggle the switch next to Android Config on.
Enter the URL to your app in the Play Store, the Package for your app and the colon separated, 
SHA256 signing certificate fingerprint(s).

Note that if you are publishing to Google Play and app signing by Google Play is enabled, 
you need to add two SHA256 fingerprints: one obtained from signing APK locally and one 
obtained from Google Play console. Fingerprints are necessary for deep linking into your app
without showing a disambiguation dialog.
To use Deferred Deep Links with your Android app, toggle the switch next to Android Config on.
Enter the URL to your app in the Play Store, the Package for your app and the colon separated, 
SHA256 signing certificate fingerprint(s).

Note that if you are publishing to Google Play and app signing by Google Play is enabled, 
you need to add two SHA256 fingerprints: one obtained from signing APK locally and one 
obtained from Google Play console. Fingerprints are necessary for deep linking into your app
without showing a disambiguation dialog.
To use Deferred Deep Links with your Android app, toggle the switch next to Android Config on.
Enter the URL to your app in the Play Store, the Package for your app and the colon separated, 
SHA256 signing certificate fingerprint(s).

Note that if you are publishing to Google Play and app signing by Google Play is enabled, 
you need to add two SHA256 fingerprints: one obtained from signing APK locally and one 
obtained from Google Play console. Fingerprints are necessary for deep linking into your app
without showing a disambiguation dialog.

Project Setup

To integrate deep linking into your project, you have to complete the following steps:

// If your app doesn't use scenes, add to your AppDelegate.swift:
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    return Optimove.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
}

// If your app uses scenes: add to SceneDelegate.swift:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let _ = (scene as? UIWindowScene) else { return }

    // Deep links from cold starts
    if let userActivity = connectionOptions.userActivities.first {
        Optimove.shared.scene(scene, continue: userActivity)
    }
}

func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
    Optimove.shared.scene(scene, continue: userActivity)
}


// Configure a deep link handler:
let cfg = OptimoveConfigBuilder(optimoveCredentials: "<YOUR_OPTIMOVE_CREDENTIALS>", optimobileCredentials: "<YOUR_OPTIMOVE_MOBILE_CREDENTIALS>")
    .enableDeepLinking({ (resolution) in
        print("Deep link resolution result: \(resolution)")
    })
    .build()
Optimove.initialize(config: cfg)

/* 
Finally, you need to associate a domain with your app. Note that the subdomain you specify 
here should match the one you specified on the deep link configuration page in the Optimove UI.

1. Add the Associated Domains capability to your main app target
2. Set 'domains' to applinks:{yourSubdomain}.lnk.click 
*/
// 1. Add an intent filter to your main activity:

<intent-filter android:label="deepLabel" android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <!-- Specify which URIs should be matched. Note, domain should be .lnk.click -->
    <data android:scheme="https" android:host="subdomain.lnk.click"/>
</intent-filter>

/*
Note that the subdomain you specify above should match the one you specified on the deep link 
configuration page on the Optimove UI.

Note that setting android:autoVerify="true" will verify ownership of the domain, so, 
instead of offering a list of apps to select from your app will open automatically.
No further actions are needed to enable this
*/

// 3. Add following overrides to the main activity:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Optimove.getInstance().seeIntent(getIntent(), savedInstanceState);
}

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    Optimove.getInstance().seeInputFocus(hasFocus);
}

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    Optimove.getInstance().seeIntent(intent);
}

// 4. Configure a deep link handler:

OptimoveConfig config = new OptimoveConfig.Builder(
    "<YOUR_OPTIMOVE_CREDENTIALS>","<YOUR_OPTIMOVE_MOBILE_CREDENTIALS>"
    ).enableDeepLinking(null, new MyDeferredDeepLinkHandler())
    .build());

Optimove.initialize(this, config);

// 5. A stub implementation of the handler could be as follows:

public class MyDeferredDeepLinkHandler implements DeferredDeepLinkHandlerInterface {
    public void handle(Context context, DeferredDeepLinkHelper.DeepLinkResolution resolution, String link, @Nullable DeferredDeepLinkHelper.DeepLink data){
       //- Inspect the data payload and run code as needed.
   }
}
/************** Android **************/

// Make sure that the initialization builder method deeplinkEnabled is called with true:
OptimoveReactNativeInitializer.initializeOptimove(
    OptimoveReactNativeConfig.newInstance()
                                .optimoveCredentials("<YOUR OPTIMOVE CREDENTIALS>")
                                .optimobileCredentials("<YOUR OPTIMOBILE CREDENTIALS>")
                                .deeplinkEnabled(true)
                                //...
                                .build(), getApplicationContext());
    
// Add an intent filter to your main activity:
<intent-filter android:label="deepLabel" android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <!-- Specify which URIs should be matched. Note, domain should be .lnk.click -->
    <data android:scheme="https" android:host="subdomain.lnk.click"/>
</intent-filter>
    
/*
Note that the subdomain you specify above should match the one you specified on the deep 
link configuration page on the Optimove UI.

Note that setting android:autoVerify="true" will verify ownership of the domain, so, instead 
of offering a list of apps to select from your app will open automatically. No further 
actions are needed to enable this.
*/
    
// Add following overrides to the main activity:
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Optimove.getInstance().seeIntent(getIntent(), savedInstanceState);
}

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    Optimove.getInstance().seeInputFocus(hasFocus);
}

@Override
public void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    Optimove.getInstance().seeIntent(intent);
}

/************** iOS **************/
// Make sure that the initialization enableDeferredDeepLinking param is true:

OptimoveInitializer.initialize(
  "<YOUR OPTIMOVE CREDENTIALS>", 
  optimobileCredentials: "<YOUR OPTIMOBILE CREDENTIALS>", 
  inAppConsentStrategy: "auto-enroll|in-app-disabled|explicit-by-user",
  enableDeferredDeepLinking: true)

/*
Associate a domain with your app. Note that the subdomain you specify here should match the one 
you specified on the deep link configuration page in the Optimove UI.

Add the Associated Domains capability to your main app target. Set 'domains' to 
applinks:{yourSubdomain}.lnk.click */

// Add the following to the AppDelegate (Objective C)
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *restorableObjects))restorationHandler {
    return [OptimoveInitializer application:application userActivity:userActivity restorationHandler:restorationHandler];
}

/************** Typescript / Javascript **************/
// Finally, configure a deep link handler:
Optimove.setDeepLinkHandler((deepLink) => {
   
});

/************** Android **************/
// Add an intent filter to your main activity:

<intent-filter android:label="deepLabel" android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <!-- Specify which URIs should be matched. Note, domain should be .lnk.click -->
    <data android:scheme="https" android:host="subdomain.lnk.click"/>
</intent-filter>

/* Note that the subdomain you specify above should match the one you specified on the deep link
configuration page on the Optimove UI.

Note that setting android:autoVerify="true" will verify ownership of the domain, so, instead of 
offering a list of apps to select from your app will open automatically. No further actions are 
eeded to enable this */

// Add following overrides to the main activity:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Optimove.getInstance().seeIntent(getIntent(), savedInstanceState);
}

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    Optimove.getInstance().seeInputFocus(hasFocus);
}

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    Optimove.getInstance().seeIntent(intent);
}

// Configure a deep link handler:

OptimoveConfig config = new OptimoveConfig.Builder(
    "<YOUR_OPTIMOVE_CREDENTIALS>","<YOUR_OPTIMOVE_MOBILE_CREDENTIALS>"
    ).enableDeepLinking(null, new MyDeferredDeepLinkHandler())
    .build());

Optimove.initialize(this, config);

// A stub implementation of the handler could be as follows:

public class MyDeferredDeepLinkHandler implements DeferredDeepLinkHandlerInterface {
    public void handle(Context context, DeferredDeepLinkHelper.DeepLinkResolution resolution, String link, @Nullable DeferredDeepLinkHelper.DeepLink data){
       //- Inspect the data payload and run code as needed.
   }
}

/************** iOS **************
First, you need to associate a domain with your app. Note that the subdomain you specify here 
should match the one you specified on the deep link configuration page in the Optimove UI. */

// Add the Associated Domains capability to your main app target. Set 'domains' to 
applinks:{yourSubdomain}.lnk.click

// Add the following to the AppDelegate.swift in your native project:

override func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
        return Optimove.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
    }
}

// To enable deferred deep linking add the flag below to your optimove.json:
{
  ...
  "enableDeferredDeepLinking": true
}

/************** Android **************/
// Modify config.xml to add the following:

<widget 
    ...
    xmlns:android="http://schemas.android.com/apk/res/android"
>
    ...
    <platform name="android">
    	<config-file parent="/manifest/application/activity" target="AndroidManifest.xml">
    		<intent-filter android:label="deepLabel" android:autoVerify="true">
    		    <action android:name="android.intent.action.VIEW" />
    		    <category android:name="android.intent.category.DEFAULT" />
    		    <category android:name="android.intent.category.BROWSABLE" />
    		    <!-- Specify which URIs should be matched. Note, domain should be .lnk.click -->
    		    <data android:scheme="https" android:host="cordova-example-optimove.lnk.click"/>
    		</intent-filter>
    	</config-file>
    
    	<edit-config file="AndroidManifest.xml" target="/manifest/application/activity/intent-filter/category/[@android:name='android.intent.category.LAUNCHER']/../.." mode="merge">
    		<activity android:name="OptimoveMainActivity"/>
    	</edit-config>
    </platform>
</widget>

/* Note that the subdomain you specify above should match the one you specified on the deep link
configuration page on the Optimove UI.

Note that setting android:autoVerify="true" will verify ownership of the domain, so, instead of 
fering a list of apps to select from your app will open automatically. No further actions are 
needed to enable this.

Make sure to run cordova prepare android to apply changes from config.xml. 

Troubleshooting:
 config.xml changes set launcher activity to OptimoveMainActivity which extends MainActivity 
 generated by Cordova. OptimoveMainActivity has overrides required for Optimove DDL to function.
 If you are having trouble with this setup you can manually copy the overrides into the activity 
 of your choice. */


/************** iOS **************/
// First, you need to associate a domain with your app. Note that the subdomain you specify here 
// should match the one you specified on the deep link configuration page in the Optimove UI.

//Add the Associated Domains capability to your main app target. Set 'domains' to 
applinks:{yourSubdomain}.lnk.click

// Next, if your generated native project contains AppDelegate (most likely), add the 
//following to the AppDelegate.m:

#import "Bridging-Header.h"

...

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *restorableObjects))restorationHandler {
    return [Optimove_Cordova application:application userActivity:userActivity restorationHandler:restorationHandler];
}

//If instead there is a SceneDelegate, add the following to the SceneDelegate.m:

#import "Bridging-Header.h"

...

- (void) scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity API_AVAILABLE(ios(13.0)){
    [Optimove_Cordova scene:scene userActivity:userActivity];
}

- (void)scene:(UIScene * _Nonnull)scene willConnectToSession:(UISceneSession * _Nonnull)session options:(UISceneConnectionOptions * _Nonnull)connectionOptions API_AVAILABLE(ios(13.0)){
    [Optimove_Cordova scene:scene session:session options:connectionOptions];
}


/************** Typescript / Javascript **************/
// Finally, configure a deep link handler:

Optimove.setDeepLinkHandler((deepLink: DeepLink) => {
   //
});
// To enable Deferred Deep Linking update your `optimove.json`:
{
  ...
  "deferredDeepLinkingHost": "<YOUR_SUBDOMAIN>.lnk.click"
}

/********************** Android **********************

Next, modify `/Assets/Plugins/Android/AndroidManifest.xml` to uncomment the following:

<!-- <intent-filter android:label="deepLabel" android:autoVerify="true">
		<action android:name="android.intent.action.VIEW" />
		<category android:name="android.intent.category.DEFAULT" />
		<category android:name="android.intent.category.BROWSABLE" />
		<data android:scheme="https" android:host="<YOUR_SUBDOMAIN>.lnk.click"/>
     </intent-filter> -->
     
Note that the subdomain you specify above should match the one you specified on the deep link configuration page on the Optimove UI.
Note that setting `android:autoVerify="true"` will verify ownership of the domain, so, instead of offering a list of apps to select from your app will open automatically. No further actions are needed to enable this.

********************** iOS **********************

DDL configuration is done automatically based on updated `optimove.json`. No additional steps required.

*********************** Usage ***********************

To configure a deep link handler:  */

Optimove.Shared.SetDeepLinkResolvedHandler ( (DeepLink ddl) =>{
   //
});

Migration Guides

This section will help you migrate to a new major version of the Optimove SDK.

iOS

Migrating from 3.x to 5.x

v5 contains all features present in v3 and adds in-app messaging, deferred deep linking, extended device support and more.

Prerequisites

In order to start using Optimove SDK v5 you must provide credentials which can be retreived from your Mobile Marketing UI. To gain access to this, please make a request to the Optimove Product Integration team.

Important Note: Optimove Mobile integration can begin as soon as you have received access to the Mobile Marketing UI, however tracking custom events and Track and Trigger functions require configuration with the Optimove Product Integration team.

Updating dependency

  • Carthage: Run carthage update.
  • Cocoapods: Run pod update
  • SPM: Select the package in Xcode under Package Dependencies, right click and select Update Package

Initialization

Update SDK initialization to the following.

//v3
let tenant = OptimoveTenantInfo(tenantToken: "<YOUR_TENANT_TOKEN>",
configName:"<YOUR_CONFIG_NAME>")

Optimove.configure(for: tenant)

//v5
let config = OptimoveConfigBuilder(
    optimoveCredentials: "your-optimove-credentials",
    optimobileCredentials: "your-optimobile-credentials"
).build()

Optimove.initialize(with: config)
  • For track and trigger integrations only Optimove credentials are required.
  • For mobile push and all other features both sets of credentials will be required before moving to production, however mobile feature integration can be tested with only the OptiMobile credentials.

Mobile Push

To migrate mobile push to v5 please perform the following steps

  • Remove your UNNotificationCenterDelegate, these methods are automatically swizzled by the SDK.

  • In your Notification Service extension project, replace the contents of NotificationService.swift with the following lines:

import UserNotifications
import OptimoveNotificationServiceExtension

class NotificationService: UNNotificationServiceExtension {
    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        OptimoveNotificationService.didReceive(request, withContentHandler: contentHandler)
    }
}
  • When you want to register the device for push and prompt the user call Optimove.shared.pushRequestDeviceToken()

  • Follow the setup steps to upload your gateway credentials to the Mobile Marketing UI.

  • To get the most of the push features review the advanced features


Migrating from 2.x to 3.x

Deprecations removed

  • Optimove#setScreenVisit(screenPathArray:screenTitle:screenCategory:), use reportScreenVisit(screenTitle:screenCategory:) instead.

  • Optimove#setScreenVisit(screenPath:screenTitle:screenCategory:), use reportScreenVisit(screenTitle:screenCategory:) instead.

  • Optimove#startTestMode(), no need to start test mode anymore. Use Optimove site for tests.

  • Optimove#stopTestMode(), no need to stop test mode anymore. Use Optimove site for tests.


Android

Migrating from 5.x to 6.x

To migrate from 5.x to 6.x you need to increase your minSdkVersion to 21 (previously 19).

Due to the min supported SDK version this release drops support for devices running versions of Android OS lower than 5.0


Migrating from 4.x to 5.x

v5 contains all features present in v4 and adds in-app messaging, deferred deep linking, extended device support and more.

Prerequisites

In order to start using Optimove SDK v5 you must provide credentials which can be retreived from your Mobile Marketing UI. To gain access to this, please make a request to the Optimove Product Integration team.

Important Note: Optimove Mobile integration can begin as soon as you have received access to the Mobile Marketing UI, however tracking custom events and Track and Trigger functions require configuration with the Optimove Product Integration team.

Updating dependency

Note that Maven artifact name changed, to start the migration update the dependency as below and sync your project.

//v4
implementation 'com.optimove.sdk:optimove-sdk:4.+'

//v5
implementation 'com.optimove.android:optimove-android:5.+'

General

Package structure has changed, please update imports:

  • Replace com.optimove.sdk.optimove_sdk.main.Optimove with com.optimove.android.Optimove.
  • Replace com.optimove.sdk.optimove_sdk.* with com.optimove.android.*

Note: If some classes still cannot be found, follow this guide until the end -- perhaps you need to remove them.

Initialization

Update SDK initialization to the following.

//v4
Optimove.configure(this, new TenantInfo("<YOUR_OPTIMOVE_TENANT_TOKEN>", "<YOUR_OPTIMOVE_CONFIG_NAME>"));

//v5
Optimove.initialize(this, new OptimoveConfig.Builder(
        "your-optimove-credentials",
        "your-optimobile-credentials")
        .build());
  • For track and trigger integrations only Optimove credentials are required.
  • For mobile push and all other features both sets of credentials will be required before moving to production, however mobile feature integration can be tested with only the OptiMobile credentials.

Mobile Push

To migrate mobile push to v5 you need to completely remove existing integration and do a clean OptiMobile integration.

Removing v4 push
  • Remove OptipushMessagingService or your custom class extending FirebaseMessagingService from your AndroidManifest.xml. You will re-add when doing clean push integration.
  • Remove enablePushCampaigns and disablePushCampaigns calls.
  • Remove deep linking. For that remove references to LinkDataExtractedListener, DeepLinkHandler, LinkDataError and remove intent filter with com.optimove.sdk.optimove_sdk.DEEPLINK action from your AndroidManifest.xml.
  • Remove notification customizations by removing com.optimove.sdk.custom-notification-icon and com.optimove.sdk.custom-notification-color from AndroidManifest.xml.
  • Make sure your project builds, fix any remaining errors
Adding v5 push

Follow the Mobile Messaging Push Setup guide.


Migrating from 3.x to 4.x

Basic migration
  • Fully migrated to AndroidX library - No backward compatibility to the support library.
  • SDK init - your BuildConfig is no longer used to automatically initialize the SDK. Use the manual initialization in your Application extending class:
Optimove.configure(this, new TenantInfo(BuildConfig.OPTIMOVE_TENANT_TOKEN, BuildConfig.OPTIMOVE_CONFIG_NAME));
  • Proguard - remove the rules related to Optimove from you Proguard file.
  • Optimove is now distributed to the Maven Central, you can remove these lines:
maven {
	url  "https://mobiussolutionsltd.bintray.com/optimove-sdk"
}

Make sure that you added mavenCentral:

buildscript {
	repositories {
	  mavenCentral()
	  //...
	}
  //...
}
  • If you consume SDK version higher or equal to 4.2.0 and your target version is lower than 31 add tools:remove="android:dataExtractionRules" to the application tag of your manifest.
Optipush
  • Setup Firebase Messaging Service for your project.

  • Optimove Product Integration team will request a key to your service account. This service account should contain a custom role with just one permission - cloudmessaging.messages.create (you can read about custom roles here, about this specific permission here)
    Optimove will use this service account to execute campaigns using your FCM.
    Follow these steps to generate this key:

    1. Go to your Google Cloud Platform console
    2. Navigate to IAM & Admin
    3. Navigate to Roles
    4. Click on "Create Role" at the top
    5. Fill the fields (As a name you can choose something like 'Optimove FCM')
    6. Click on "Add Permissions"
    7. Search for cloudmessaging.messages.create and choose it
    8. Click create
    9. Navigate to Service Accounts
    10. Click on "Create Service Account" at the top
    11. Select the custom role you created in 8
    12. Click done
    13. Click on the service account you generated
    14. Generate a key to this service account

    If you have multiple Fiebase projects (for example, one Firebase project for production and one for staging), please provide for every project:

    • A key to a service account
    • List of app namespaces
  • If you don't have your own FirebaseMessagingService extending service, register OptipushMessagingService inside your AndroidManifest.xml:

<application
  <service  
    android:name="com.optimove.sdk.optimove_sdk.optipush.messaging.OptipushMessagingService"  
    android:exported="false">  
    <intent-filter>             
      <action android:name="com.google.firebase.MESSAGING_EVENT"/>  
    </intent-filter>
  </service>
</application>
  • If you do have your own FirebaseMessagingService extending service, update the onNewToken method as shown below:
// change:
new OptipushFcmTokenHandler().onTokenRefresh();

// to:
Optimove.getInstance().fcmTokenRefreshed();

Migrating from 2.x to 3.x

Deprecations removed
  • Optimove#setScreenVisit(Activity activity, String screenTitle), use Optimove#reportScreenVisit(String screenName) instead.

  • Optimove#setScreenVisit(Activity activity, String screenTitle, String screenCategory), use Optimove#reportScreenVisit(String screenName, String screenCategory) instead.

  • Optimove#setScreenVisit(String screenPath, String screenTitle), use Optimove#reportScreenVisit(String screenName) instead.

  • Optimove#setScreenVisit(String screenPath, String screenTitle, String screenCategory), use Optimove#reportScreenVisit(String screenName, String screenCategory) instead.

  • Optimove#startTestMode(SdkOperationListener operationListener), no need to start test mode anymore. Use Optimove site for tests.

  • Optimove#stopTestMode(SdkOperationListener operationListener), no need to stop test mode anymore. Use Optimove site for tests.

  • Optimove#registerSuccessStateListener(OptimoveSuccessStateListener stateListener), no need to register for lifecycle events, you can use the SDK directly.

  • Optimove#unregisterSuccessStateListener(OptimoveSuccessStateListener stateListener), no need to unregister from lifecycle events.