Flutter
Overview
Getting started for apps that are built with Flutter.
Installation
Make sure you're using a version of Flutter that we support: >=2.5.0
Add the MovableInk Plugin as a dependency
In your code, you can import it:
Android Setup
Android Deeplinking
You can follow the instructions on the Android Deeplinking article to learn more.
Android Behavior Events
If you want to use the Behavior Events features of the SDK, you'll need an API Key to enable Behavior Events for your app. If you don't already have one, please reach out to your CX Team at MovableInk with your applications bundle identifier (both Android and iOS).
Open local.properties
file, found at android > local.properties
Within the application's local.properties
, the provided API Key should be specified as follows:
Also, in the application manifest file the following meta-data
tag should be specified within the application
tag:
<meta-data
android:name="com.movableink.inked.API_KEY"
android:value="${MOVABLE_INK_SDK_API_KEY}"
/>
Lastly, in the Application level gradle file, under the defaultConfig
section:
defaultConfig {
....
manifestPlaceholders["MOVABLE_INK_SDK_API_KEY"] = localProperties['MOVABLE_INK_SDK_API_KEY']
....
}
Configuring The SDK Region:
To ensure compliance with regional requirements , we now require you to specify your region(importantly for EU clients). This is necessary to properly route your data and comply with local regulations.
In your project’s build.gradle (or build.gradle.kts) file, define a gradle property to your project’s defaultConfig block to specify the region:
defaultConfig {
// Existing configurations...
// Set your region (default is US, set EU for European clients)
buildConfigField("String", "MOVABLE_INK_SDK_REGION", "EU")
}
If you don't supply a region, the SDK will default to the US region. If you are unsure which region your company was setup in on the MovableInk Platform, please reach out to your CX team at MovableInk.
After making changes to the gradle file, perform a gradle sync.
iOS Setup
iOS Deeplinking
Open Xcode and open your projects xcworkspace:
File > Open > project/ios/Runner.xcworkspace
In the Info.plist
, add the movable_ink_universal_link_domains
key as an array containing all the domains that you'd like the MovableInk SDK to handle. Note: These should only be domains that are shown in your Creative Tags, such as mi.example.com
:
You can also edit the Info.plist
in the Project Settings.
Navigate to the Project Settings, then to the Info tab.
Here, you can add the movable_ink_universal_link_domains
key as an array:
Under the Signing & Capabilities tab, add the Associated Domains Capability, then add the applinks that you can support. These should include the same domains as the ones in the movable_ink_universal_link_domains
in the step before.
For example, if your MovableInk Domain is mi.example.com
, you'd want to add
applinks:mi.example.com
.
If you want to deeplink to your app via a custom scheme, such as myapp://products/1
, you'll need to register this custom scheme in your info plist.
iOS Behavior Events
You'll need an API Key to enable Behavior Events for your app. If you don't already have one, please reach out to your CX Team at MovableInk with your applications bundle identifier.
Once you have an API Key, you can add two key/value pair in your applications Info.plist, movable_ink_api_key and movable_ink_region.
movable_ink_region must be one of following:
- us
- eu
<key>movable_ink_api_key</key>
<string>YOUR_API_KEY</string>
<key>movable_ink_region</key>
<string>eu</string>
If you don't supply a region, the SDK will default to the US region. If you are unsure which region your company was setup in on the MovableInk Platform, please reach out to your CX team at MovableInk.
Deeplinking
Automatic Link Resolution
You can be notified of an incoming deeplink that MovableInk resolves by listening to the Stream.
MovableInkPlugin.start()
returns a Stream<String>
that will be called anytime MovableInk is able to resolve an incoming deeplink.
The data will be the clickthrough URL for the link that opened the app.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:movable_ink_plugin/movable_ink_plugin.dart';
class _MyHomePageState extends State<MyHomePage> {
final _movableInkPlugin = MovableInkPlugin();
String _resolvedURL = '';
StreamSubscription<String>? streamSubscription;
@override
void initState() {
super.initState();
initMovableInkListener();
}
@override
void dispose() {
super.dispose();
streamSubscription?.cancel();
}
// Starts listening to the MovableInkPlugins event when it resolves an
// incoming deeplink into the clickthrough link
void initMovableInkListener() async {
streamSubscription = _movableInkPlugin.start().listen((String data) {
// Save the resolved url into state and update your UI to show the
// corresponding page
setState(() {
_resolvedURL = data;
});
}, onError: (error) {
debugPrint('error: ${error.toString()}');
});
}
}
Manual Link Resolution
You can also manually resolve a URL into a clickthrough link if need be. This will ask MovableInk to attempt to resolve a URL into a clickthrough link.
void resolveUrl(String url) {
_movableInkPlugin.resolveUrl(url).then((value) {
setState(() {
_resolvedURL = value ?? '';
});
});
}
Last Resolved URL
If you received an incoming deeplink and MovableInk resolved it, but you weren't ready to handle it, say you showed a login screen first, you can call lastResolvedURL
to fetch the last url MovableInk resolved.
Note
You should try to handle this yourself if possible by handling the storing the resolved link from the stream and passing it to the needed views. If for some reason the resolution occurs after you read the value of this method, you could miss the deeplink.
The stream given via start
will always be called after resolution occurs and is much safer to depend on.
Deferred Deeplinking
When a user attempts to open a MovableInk Link that is designated as a deferred deeplink on an iOS device, and they don't already have your app installed, MovableInk will enable Deferred Deeplinking. Instead of being directed to your website experience, they will be shown a page to open the App Store to download your app.
Once downloaded, MovableInk can check the pasteboard for the original link and allow you to open that location inside your app instead.
Before you can use Deferred Deeplinking, you'll need to setup the sdk_install
event schema.
You can read more about integrating Deferred Deeplinking here.
After you've setup Deferred Deeplinking, you'll need to enable the app install event:
Open the AppDelegate.swift
file, import MovableInk and enable the app install event.
import UIKit
import Flutter
import MovableInk
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
...
MIClient.appInstallEventEnabled = true
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
MIClient.appInstallEventEnabled(true)
Now check the pasteboard. Make sure to only call this after you've called start
and setup the stream subscription.
Warning
If this is ran on iOS 16+, this will prompt the user for permission to read from the pasteboard.
Behavior Events
Setting MIU (mi_u)
An MIU is an identifier used by your companies marketing team and email/mobile messaging provider, that when shared with MovableInk, allows to uniquely identify each of your customers when the interact with campaigns or any other MovableInk content.
Most distribution partners provide a unique user identifier as an available merge tag that distinctly identifies every recipient on your list(s). Using this merge tag allows MovableInk to provide users with a consistent experience when opening the same email multiple times and across multiple devices. Providing an MIU is required for accurate behavior and conversion tracking.
You'll need to work with your distribution partners to identify a unique user identifier. It must meet the following criteria:
- Unique per recipient
- Does not vary between individual sends for that channel
- A URL-friendly string What does URL friendly mean?
- Does not contain personally identifying information (PII) What qualifies as PII?
You should make the following call as soon as the user is identified (usually after logging in) to set the MIU. Double check with your marketing team that the MIU you're using matches the values they are using. It's imperative that they match for accurate behavior event tracking.
If your app has support for guest/anonymous users, you can still track events without setting an MIU. Once a user does login, you should call _movableInkPlugin.setMIU()
with the MIU of the user, then call _movableInkPlugin.identifyUser();
. This will attempt to associate any events that user made as a guest to this user.
Inherited MIUs
If a user interacts with a MovableInk Link that contains an MIU and deeplinks into your app, the SDK will use that MIU value automatically for all subsequent behavior events if you don't manually set an MIU.
If you manually set an MIU, events will use that instead.
Suggested MIU naming convention
We strongly recommend you use a UUID for MIUs.
If you find your ids include names, email addresses, timestamps, or are easily incremental (1, 2, 3), we suggest using a new naming method that is more secure so that your MIUs are not as easy to guess or impersonate.
Recommended | NOT Recommended |
---|---|
42772706-c225-43ad-837e-c01e94907c3c | user@example.com |
d68d3dbe-86e1-43ce-bf5f-2dafd2f6af45 | 123456789 |
6ec4f1dd-0998-4ca8-8793-7511c2625a45 | john-smith-123 |
Product Searched
Key | Type | Description | Max |
---|---|---|---|
query | String, required | The query the user used for a given search | 256 |
url | String | A URL for the given query | 512 |
final Map<String, dynamic> properties = {
"query": "hyperion",
"url": "https://inkrediblebooks.com/search?q=hyperion"
};
_movableInkPlugin.productSearched(properties);
Product Viewed
Key | Type | Description | Max |
---|---|---|---|
id | String, required | The ID of a product | 256 |
title | String | The title of the product | 512 |
url | String | The URL of the product | 512 |
categories | List<Map<String, String>> | A List of Categories associated with the product. A Category is a Map<String, String> that must contain an id (String) and optionally a url | 10 Items |
meta | Map<String, dynamic> | An open Map of any extra data you'd like to associate to this event. The Value must be either a String, Boolean, or Numeric. Meta does not accept nested objects or arrays. | 20 Items |
final Map<String, dynamic> properties = {
"id": "1",
"title": "Hyperion",
"price": "15.99",
"url": "https://inkrediblebooks.com/hyperion-dan-simmons",
"categories": [
{
"id": "Sci-Fi",
"url": "https://inkrediblebooks.com/categories/scifi"
},
{
"id": "Fiction",
"url": "https://inkrediblebooks.com/categories/fiction"
}
],
"meta": { "pages": 496 }
};
_movableInkPlugin.productViewed(properties);
Product Added
Key | Type | Description | Max |
---|---|---|---|
id | String, required | The ID of a product | 256 |
title | String | The title of the product | 512 |
price | String | The price of the product in Dollars and Cents. Do not include currency or any other non-decimal digit characters. | |
url | String | The URL of the product | 512 |
categories | List<Map<String, String>> | A List of Categories associated with the product. A Category is a Map<String, String> that must contain an id (String) and optionally a url | 10 Items |
meta | Map<String, dynamic> | An open Map of any extra data you'd like to associate to this event. The Value must be either a String, Boolean, or Numeric. Meta does not accept nested objects or arrays. | 20 Items |
final Map<String, dynamic> properties = {
"id": "1",
"title": "Hyperion",
"price": "15.99",
"url": "https://inkrediblebooks.com/hyperion-dan-simmons",
"categories": [
{
"id": "Sci-Fi",
"url": "https://inkrediblebooks.com/categories/scifi"
},
{
"id": "Fiction",
"url": "https://inkrediblebooks.com/categories/fiction"
}
],
"meta": { "pages": 496 }
};
_movableInkPlugin.productAdded(properties);
Product Removed
Key | Type | Description | Max |
---|---|---|---|
id | String, required | The ID of a product | 256 |
title | String | The title of the product | 512 |
price | String | The price of the product in Dollars and Cents. Do not include currency or any other non-decimal digit characters. | |
url | String | The URL of the product | 512 |
categories | List<Map<String, String>> | A List of Categories associated with the product. A Category is a Map<String, String> that must contain an id (String) and optionally a url | 10 Items |
meta | Map<String, dynamic> | An open Map of any extra data you'd like to associate to this event. The Value must be either a String, Boolean, or Numeric. Meta does not accept nested objects or arrays. | 20 Items |
final Map<String, dynamic> properties = {
"id": "1",
"title": "Hyperion",
"price": "15.99",
"url": "https://inkrediblebooks.com/hyperion-dan-simmons",
"categories": [
{
"id": "Sci-Fi",
"url": "https://inkrediblebooks.com/categories/scifi"
},
{
"id": "Fiction",
"url": "https://inkrediblebooks.com/categories/fiction"
}
],
"meta": { "pages": 496 }
};
_movableInkPlugin.productRemoved(properties);
Category Viewed
Key | Type | Description | Max |
---|---|---|---|
id | String, required | The ID of a category | 256 |
title | String | The title of the category | 512 |
url | String | The URL of the category | 512 |
final Map<String, dynamic> properties = {
"id": "scifi",
"title": "Sci-Fi",
"url": "https://inkrediblebooks.com/categories/scifi"
};
_movableInkPlugin.categoryViewed(properties);
Order Completed
Key | Type | Description | Max |
---|---|---|---|
id | String | The ID of a order | 256 |
revenue | String | The total of the order in Dollars and Cents. Do not include currency or any other non-decimal digit characters. | |
products | List<Map<String, dynamic>> | A List of the products in an order. Product is a Map<String, dynamic>, requiring an id, and optionally a price, quantity, title, and/or url. | 100 Items |
final Map<String, dynamic> properties = {
"id": "1",
"revenue": "15.99",
"products": [
{
"id": "1",
"price": "15.99",
"quantity": 1,
"title": "Hyperion",
"url": "https://inkrediblebooks.com/hyperion-dan-simmons"
}
]
};
_movableInkPlugin.orderCompleted(properties);
Custom Defined Events
In addition to the pre-defined events we provide, you can log custom events tailored to your business specific requirements. This flexibility enables you to monitor and understand user interactions beyond what our pre-defined events offer.
This type of event has a custom name name, such as video_started
, and optional parameters as the payload.
The name and properties of a custom event must first be defined on your companies account. Please reach out to your CX Team at MovableInk to get started with this.
Identity
Identify User
If a user has used your app as a guest, then logs in, you can attempt to associate any events that user made as a guest using the identifyUser
method.
Testing Behavior Events
When you're ready to test an event, you need to set the MIU to a known value. You should let your CX Team know that you're ready to start testing events and give them the MIU that you are going to use before you do so. This will allow them to verify that the events are being sent correctly.
We usually use an empty UUID for testing, such as 00000000-0000-0000-0000-000000000000
.
When you're ready to test an event, you should open the Console
app on your Mac, select your device on the sidebar, then press the start button on the top bar. To filter the logs to just view MovableInk SDK logs, search for MI SDK
.
When you send an event, you should see the event logged in the console. If the event was structured correctly, you should see a success
message in the console along side the event name.
In App Message
The MovableInk SDK supports showing HTML based MI Content as In App Messages.
// Create stream subscription
StreamSubscription inAppMessageStreamSubscription;
inAppMessageStreamSubscription = _movableInkPlugin.subscribeToInAppMessageInteraction((String buttonId) {
// User interacted with a button that contains a buttonId in the in app message
});
// Cancel stream subscription
inAppMessageStreamSubscription.cancel();
_movableInkPlugin.showInAppMessage("https://mi.example.com/p/rp/abcd12345.html");
Note the link ends with .html
. When exporting your links from Studio, make sure to add this suffix when using it as an In App Message in the SDK.
If you're already using an In App Message provider, such as Braze or Salesforce Marketing Cloud, you can still use MovableInk driven messages from those tools.
Go to the In App Message article to learn more.