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 theSDK 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 theSDK 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/orsetUserId/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
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 onlysnake_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
orfalse
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:
-
Select
File > New > Target
on XCode -
Select the Notification Service Extension target from the
iOS > Application
section -
Click
Next
-
Specify a name for your extension
-
Click
Finish
-
In your
Podfile
add a new target matching the extension's name -
Locate the
Notification Service Extension
'starget
declaration -
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 pictures, delivery tracking, dismissed tracking and badges
For push delivery tracking, dismissed tracking and badges to work correctly you need to
- Set up a Notification Service Extension as described above
- Add the
App Groups
capability to your App and Notification Service Extension targets - 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 tappedgetNotificationBuilder
-- to customize notification (*)onBackgroundPush
-- to perform work when a background data push is receivedonPushReceived
-- 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 anActivity
, 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
FirebaseMessagingService
with OptimoveIf 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:)
, usereportScreenVisit(screenTitle:screenCategory:)
instead. -
Optimove#setScreenVisit(screenPath:screenTitle:screenCategory:)
, usereportScreenVisit(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
withcom.optimove.android.Optimove
. - Replace
com.optimove.sdk.optimove_sdk.*
withcom.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 extendingFirebaseMessagingService
from yourAndroidManifest.xml
. You will re-add when doing clean push integration. - Remove
enablePushCampaigns
anddisablePushCampaigns
calls. - Remove deep linking. For that remove references to
LinkDataExtractedListener
,DeepLinkHandler
,LinkDataError
and remove intent filter withcom.optimove.sdk.optimove_sdk.DEEPLINK
action from yourAndroidManifest.xml
. - Remove notification customizations by removing
com.optimove.sdk.custom-notification-icon
andcom.optimove.sdk.custom-notification-color
fromAndroidManifest.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:- Go to your Google Cloud Platform console
- Navigate to IAM & Admin
- Navigate to Roles
- Click on "Create Role" at the top
- Fill the fields (As a name you can choose something like 'Optimove FCM')
- Click on "Add Permissions"
- Search for
cloudmessaging.messages.create
and choose it - Click create
- Navigate to Service Accounts
- Click on "Create Service Account" at the top
- Select the custom role you created in 8
- Click done
- Click on the service account you generated
- Generate a key to this service account
If you have multiple Firebase 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, registerOptipushMessagingService
inside yourAndroidManifest.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 theonNewToken
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)
, useOptimove#reportScreenVisit(String screenName)
instead. -
Optimove#setScreenVisit(Activity activity, String screenTitle, String screenCategory)
, useOptimove#reportScreenVisit(String screenName, String screenCategory)
instead. -
Optimove#setScreenVisit(String screenPath, String screenTitle)
, useOptimove#reportScreenVisit(String screenName)
instead. -
Optimove#setScreenVisit(String screenPath, String screenTitle, String screenCategory)
, useOptimove#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.
Updated 6 days ago