Contentsquare Flutter Integration
Introduction
Flutter support is currently only accessible to customers participating in the Early Access program. For more information reach out to your Contentsquare contact.
Get Started
This plugin relies on the Contentsquare native SDKs to log events.
It exposes a unified surface.
For more information, refer to the dedicated SDK docs:
đ iOS documentation
Installation
Add the contentsquare
package in your pubspec.yaml
:
dependencies:
flutter:
sdk: flutter
contentsquare: ^1.0.0
then run
flutter pub get
iOS
To ensure the best experience, you should target 10+ iOS version.
Add this line at the top of your Podfile
:
platform :ios, '10.0'
Android
The plugin works out of the box.
Start the SDK
You do not need to do anything to start the SDK. Now that the SDK is a dependency of your app, it will autostart itself when your application starts.
Validate SDK integration
Look at the native SDK docs for validation on each platform.
Note that iOS logs should be viewed through Console or Xcode.
Flutter Setup
Wrap the entire application inside the ContentsquareRoot
widget:
import 'package:flutter/material.dart';
import 'package:contentsquare/contentsquare.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ContentsquareRoot( // <-- Wrap the entire app inside the ContentsquareRoot widget imported from 'package:contentsquare/contentsquare.dart'
child: MaterialApp(
title: 'My Flutter App',
home: Home()
),
);
}
}
In-app features
Alongside its tracking capabilities, the SDKs embed some features aimed at Contentsquare users such as Snapshot Capture and SDK Logs.
Implement in-app features (iOS Only)
In order to allow Contentsquare users to enable in-app features, you must perform 2 implementation tasks:
1. Add the custom URL scheme in your app Info
You have to allow your app to be opened via a custom URL scheme which can be done using one of the following methods:
[tab] Xcode
- Open your project settings
- Select the app target
- Select the
Info
settings - Scroll to
URL Types
- Set the URL scheme to
cs-$(PRODUCT_BUNDLE_IDENTIFIER)
[tab] Text editor
-
Open the
Info.plist
of your project -
Add the following snippet:
Info.plist<key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleURLSchemes</key> <array> <string>cs-$(PRODUCT_BUNDLE_IDENTIFIER)</string> </array> </dict> </array>
[tabend]
2. Call the SDK when the app is launched via a deeplink
In ios/Runner/AppDelegate.swift
, add a new method to the AppDelegate
class:
override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
Contentsquare.handle(url: url)
return super.application(app, open: url, options: options)
}
Here is what your AppDelegate
class should look like after adding the in-app features:
import UIKit
import Flutter
import ContentsquareModule
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// Add this method:
override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
Contentsquare.handle(url: url)
return super.application(app, open: url, options: options)
}
}
Enable in-app features
In-app features can be enabled in different ways:
Scan the QR Code
If you have access to the Contentsquare platform, you can open the in-app features modal from the menu and scan the QR code displayed with your phone.
Take a look at the native documentation for further information.
Alternative methods
We provide alternative methods to enable in-app features especially for iOS Simulator and Android Emulator.
[tab] Android
Follow the Native Android SDK documentation for Enable in-app features
[tab] iOS
Follow the Native iOS SDK documentation for Enable in-app features
Debugging and Logging
Contentsquare provides Logging capabilities that allow you to see the raw event data logged by your app. This is very useful for validation purposes during the instrumentation phase of development and can help you discover errors and mistakes in your analytics implementation and confirm that all events are being logged correctly.
[tab] Android
Follow the Native Android SDK documentation for Debugging and Logging
[tab] iOS
Follow the Native iOS SDK documentation for Debugging and Logging
Snapshot Capture
In order to unlock the full data-visualization capabilities of Contentsquare, the SDK provides a way to capture snapshots of your app screens. These snapshots can only be taken by Contentsquare's users on their device. They are not captured from your end-users device. It means your Personal Data is safe, as long as you use a test user account.
[tab] Android
Follow the Native Android SDK documentation for Snapshot Capture
[tab] iOS
Follow the Native iOS SDK documentation for Snapshot Capture
Usage
The plugin can be used through a Contentsquare
class instance:
import 'package:contentsquare/contentsquare.dart';
Contentsquare contentsquare = Contentsquare();
Since all the functions are using a PlatformChannel to send request to the native platform, all functions are Future
and should be awaited and caught.
Contentsquare contentsquare = Contentsquare();
try {
final _userId = await contentsquare.getUserId();
setState(() {
userId = _userId;
});
} on PlatformException catch (e) {
// Handle error message
}
Privacy
The Contentsquare Flutter plugin and SDKs are compliant with the Play Store and App Store Privacy guidelines as well as with the EU General Data Protection Regulation (GDPR).
Consult our Privacy Center and Privacy Policy.
Handling User Consent
Even though Contentsquare only collects usage data on your app, we will consider every new user to be opted-out. To start tracking, the Opt-in API must be called.
Opt-in
Use the Opt-in API to get user consent. Calling this API will generate a user ID and initiate tracking.
await contentsquare.optIn();
Opt-Out
Permanently breaking the link and stopping all data collection.
When this API is called, tracking stops immediately, all settings are reset (session number, page number, user ID...) and all files related to Contentsquare tracking are deleted. Contentsquare will never track and collect any data from the user's phone unless the Opt-in API is called again.
await contentsquare.optOut();
Forget me
Permanently breaking the link between the collected data and actual user.
This resets all settings (session number, page number, user ID...) and deletes all files related to Contentsquare tracking from the user's device. If the user is opted in, next time the user starts the app, the SDK will re-start its collection mechanisms as if this was the first ever run for a new user, under a new user ID. Configurations will be fetched from the server and application tracking will be on.
await contentsquare.forgetMe();
Give me my data
We allow the client to provide to their users their Contentsquare user ID.
This ID is a non binding identifier which can be used to make a data request to Contentsquare. You are able to get an ID only if the user is not Opted-out.
final userId = await contentsquare.getUserId();
Stop / Resume Tracking
Although we do not gather any humanly readable text from the user's screens, we understand that there may be some areas that you want to completely exclude from tracking. For this reason, we also support stopping and resuming the complete tracking mechanism.
await contentsquare.stopTracking();
await contentsquare.resumeTracking();
Track Screens
Contentsquare aggregates user behavior and engagement at the screen level. To do so, it is required to track screen transitions by calling a dedicated API. When the API is called, the SDK logs a screenview event that identifies the new screen with the screen name provided.
await contentsquare.send('ScreenName');
Screen name handling
The screen name length is not limited on the SDK side. However, the limit is 2083 characters on the server side.
Screenview after app in background
The SDK triggers a screenview automatically after the app is put in background and foreground, as long as a screenview with a screen name has been triggered previously. It will use the last screen name set.
Implementation recommendations
From a functional standpoint, we expect a screenview to be sent:
- When the screen appears
- When a modal/pop-up is closed and the user is back on the screen
- When the app is put in the foreground (after an app hide)
How to name screens
As a general rule, keep distinct screen names under 100. As they are used to map your app in Contentsquare, you will want something comprehensive.
Separate words with space, dash or underscore characters
If you want to generate screen names including more than one word, it is best to separate them and to do so using space, dash or underscore characters. Contentsquare handles automatically the formatting for them.
Example: For a sub-category list of a retail app, use Home & Living - Home Furnishings
instead of .homeLivingHomeFurnishings
Use screen template/layout names
As a general recommendation, use names referring to the screen template/layout rather than referring to the specific content (data). This will help to keep the number of distinct screen names low and therefore make Contentsquare easier to use remove the risk of sending Personal Data to Contentsquare List of screen types falling into that category: Product detail, Event detail, Conversation/Chat, User profile...
Multiple layouts/states for one screen
In some cases, there will be screen that can have different layouts/states depending on the user context. In this situation, you could append the layout/state value to the screen name. Examples:
- Home screen of a travel app adapting its layout on the user context:
State | Screen name |
---|---|
No trip planned | Home - no trip |
Trip planned | Home - trip planned |
Trip about to start | Home - upcoming trip |
Trip in progress | Home - trip in progress |
- Product detail screen of an e-commerce app with different layouts depending on the type of product:
State | Screen name |
---|---|
Default template | Product detail |
Template with suggested products | Product detail - Suggestions |
Template with bundled products | Product detail - Bundle |
Track Transactions
To associate a user's session with their potential purchases (and corresponding revenue), you must send the transaction via a dedicated API. For each transaction, we send:
price
(double
, required)currency
(String
, required)transactionId
(String
, optional)
await contentsquare.sendTransaction(430, 'EUR', "testId");
You can also use the Currency helper to get the list of ISO 4217 currencies.
await contentsquare.sendTransaction(430, Currency.EUR.toStr());
If the currency passed doesn't match our supported currencies, the SDK will send a currency value of "-1". It will be processed as the default currency of the project.
Track Dynamic Variables
General principles
Usage
Dynamic variables are additional information on the session that can be used to segment sessions.
For example, they can include information on the A/B Test variations displayed to the current user.
Limits
On the server side
- It is possible to save up to 40 distinct dynamic variable keys per screen view. If more are received, only the first 40 keys will be kept.
- If you are using the same key twice, the last value associated with the key will be collected.
On the SDK side
- Every dynamic variable is composed of a pair of key (max. 50 characters) and value (max. 255 characters string or number of type Long between 0 and 232 - 1). In case these maximums length are reached, the SDK will automatically trim the exceeding characters.
- If key or value are empty, the SDK will instead send the literal string "cs-empty".
Defining dynamic variables
To define and send a dynamic variable, you just need to use the following API. For each dynamic variable, we send:
key
(String
, required)stringValue
(String
, optional)intValue
(int
, optional)
Note that exactly one of stringValue
or intValue
should be used at the same time.
Also, in order for you to be able to handle such errors happening when we try to send the dynamic variable, you should wrap your call in a try/catch as explained in the Usage part.
await contentsquare.sendDynamicVar('my key 1', stringValue: 'testValue');
await contentsquare.sendDynamicVar('my key 1', intValue: 12);
Type of the value â The value can be either a number or a string. For each case, available features won't be the same in the Contentsquare app:
- For
int
, you will be able to do some algebra. Example: sessions with dynamic variable key = "numberOfFriends" and value >= 10 - For
String
, auto-completion and Regular Expression will be available. Example: sessions with dynamic variable key = "accountType" and value is "Premium"
Sending a dynamic variable for each session
You may want to send a dynamic variable for each session (like the user's country or store). While triggering the dynamic variable at app launch will cover most cases, it will not be enough. A session can start when the app is put in foreground, after staying in background for more than 30 minutes. See Session definition section for more information.
That is why we also recommend sending such dynamic variable every time the app enters foreground.
You can use didChangeAppLifecycleState
method to detect foreground and trigger a dynamic variable.
Track WebViews (Beta)
To enable WebView tracking, it is required to build a JavaScript plugin between the content of the WebView and the native SDK. To do so, you will have to implement the Contentsquare WebView JavaScript Tracking Tag in the web pages called in your app WebViews.
đ WebView JavaScript Tracking Tag Documentation
Once the WebView Tracking Tag is implemented in the web pages, you need to wrap any tracked WebView in the ContentsquareWebViewTrackerBuilder Widget. See:
- WebView Flutter if you are using the official WebView plugin
- Generic API if you are using any other package
WebView Flutter
If you are using the official WebView plugin, the following code can be used to track a webview:
ContentsquareWebViewTrackerBuilder(
builder: (context, tracker) {
return WebView(
initialUrl: 'https://your_tracked_webview.com',
javascriptMode: JavascriptMode.unrestricted,
javascriptChannels: tracker.javascriptChannels,
onWebViewCreated: tracker.onWebViewCreated,
onPageFinished: tracker.onPageFinished,
);
},
)
Generic API
To use a custom WebView package, you can use the ContentsquareWebViewTrackerBuilder.custom
widget constructor.
The builder gives you a tracker of which you need to consider the following elements:
tracker.registerWebViewController
tracker.javascriptChannels
: Register these channels which are used to communicate between Contentsquare and your WebViewtracker.initializeWebViewTracking
: This must be called after registerWebViewController and when thejavascriptChannels
are registered.
See the following section for a concrete example of implementation with the flutter_inappwebview package.
Flutter InAppWebView
Here is the generic API, applied to the tracking of webviews from the flutter_inappwebview package:
ContentsquareWebViewTrackerBuilder.custom(
builder: (context, tracker) {
return InAppWebView(
initialUrlRequest: URLRequest(
url: Uri.parse('https://your_tracked_webview.com'),
),
onLoadStop: (controller, uri) {
// 1. Register the ContentsquareWebViewController by mapping InAppWebView's controller
tracker.registerWebViewController(
ContentsquareWebViewController(
evaluateJavascript: (source) async {
return '${await controller.evaluateJavascript(source: source)}';
},
currentUrl: () async => '${await controller.getUrl()}',
javascriptChannelCall: (channelName, data) {
return 'window.flutter_inappwebview.callHandler("$channelName", $data)';
},
),
);
// 2. Register the javascript channels
for (final javascriptChannel in tracker.javascriptChannels) {
controller.addJavaScriptHandler(
handlerName: javascriptChannel.name,
callback: (args) {
return javascriptChannel.onMessageReceived(args.first);
}
);
}
// 3. Initialize the tracking.
//
// This MUST be called AFTER the controller is registered and the javascript
// channels are available
tracker.initializeWebViewTracking();
},
);
},
)
Validate WebView tracking
Validating the implementation on the Flutter side
Once you arrive on the screen with the tracked WebView, you should see the following log:
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â âšī¸ INFO âšī¸ (CSLIB) [Tracked WebView]
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â WebView tracking enabled on WebView with initialUrl:
â https://your.tracked.website
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
Validating the implementation on the web side
Once you arrive on the screen with the tracked WebView, you should see the following log:
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â âšī¸ INFO âšī¸ (CSLIB) [Tracked WebView] âļ Event "sendMessage"
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â {"message":"ready, v:06c8d9bbfdf0e4bb9041b784e95c751873654cb0"}
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
How the SDK works
Initialization
The way our SDK works is by auto-starting with the application launch. While native SDKs can intercept gesture events automatically, Flutter apps require to wrap the application into a ContentsquareRoot widget.
Configuration
Once started, our SDK fetches its config from our servers, and then depending on the segmentation size (defined by our team when you sign a contract) it will start collecting data from system and user events it detects from the runtime.
Tracking
The SDK monitors the application lifecycle events and the view hierarchy, and generates analytics data from the behavior of the app, the content of the screen and the interaction of the user. These events are then locally stored, and eventually sent to our servers in batches. We then aggregate that data to create usable visual information into our Web Application, which you use to gather insights.
Sending data
[tab] Android
Checkout the Native Android SDK documentation for Sending data
[tab] iOS
Checkout the Native iOS SDK documentation for Sending data
Session definition
A session represents a single period of user interaction in the app.
In Contentsquare, a session ends when the user has spent a certain amount of time outside the app. The SDK checks this when an app start
or app show
event is detected. This time spent outside the app, ending a session is set to 30 minutes by default. But it can be changed if it is requested.
If the app is put in the background or killed (intentionally by the user or by the OS) but the user comes back within 30 minutes, it will not end the session. These events are considered to be part of a session.
Security
Transmission and hosting
Our server side uses HTTPS to makes sure that data is encrypted in transport.
Our hosting solution is a PCI DSS Level 1 Service Provider.
What is not collected
- Passwords from password fields
- Geo location information
- Information from contacts, emails, or any other user identifiable information.
- Text from labels, buttons, and any other widget which contains text
- Accessibility information from any widget which the user interacts with
- Labels printed on the screen of any kind.
Compatibility
Each version of the Contentsquare Flutter plugin will be linked to specific, fixed versions of each native SDK. In order to make that clear, here is a compatibility table to consider when choosing your version of the plugin (refer to the Flutter plugin Changelog to know what the changes are for each version).
Contentsquare Flutter plugin Version | iOS SDK Version | Android SDK Version | Minimum Flutter Version |
---|---|---|---|
1.3.2 | 4.18.0 | 4.15.0 | 2.8.0 |
1.3.1 (deprecated) | 4.18.0 | 4.15.0 | 2.8.0 |
1.3.0 | 4.17.0 | 4.14.0 | 2.8.0 |
1.2.0 | 4.13.0 | 4.13.1 | 2.8.0 |
1.1.0 | 4.13.0 | 4.13.1 | 2.8.0 |
1.0.1 | 4.13.0 | 4.13.1 | 2.0.0 |
1.0.0 | 4.13.0 | 4.11.0 | 2.0.0 |
0.5.3 | 4.13.0 | 4.11.0 | 2.0.0 |
0.5.2 | 4.13.0 | 4.9.0 | 2.0.0 |
0.5.1 | 4.13.0 | 4.9.0 | 2.0.0 |
0.5.0 | 4.13.0 | 4.9.0 | 2.0.0 |
0.4.2 | 4.12.0 | 4.8.0 | 2.0.0 |
0.4.1 | 4.11.0 | 4.8.0 | 2.0.0 |
0.4.0 | 4.11.0 | 4.7.0 | 2.0.0 |
0.3.0 | 4.11.0 | 4.7.0 | 2.0.0 |
0.2.3 | 4.10.1 | 4.7.0 | 2.0.0 |
0.2.2 | 4.10.0 | 4.3.0 | 2.0.0 |
0.2.1 | 4.6.0 | 4.3.0 | 2.0.0 |
0.2.0 | 4.6.0 | 4.3.0 | 2.0.0 |
0.1.0 | 4.3.0 | 4.1.0 | 2.0.0 |
Troubleshooting
SwiftContentsquarePlugin was registered twice
There is a known issue with the background_locator
plugin which causes Contentsquare plugin to be registered twice. This issue has been mitigated and you can ignore this warning.
If you are not using the background_locator
plugin and encounter this warning, contact us.
Requests are failing
In order for Contentsquare to work, you need to make sure the following endpoints are not blocked by your network (VPN):
Request | Endpoint | Detail |
---|---|---|
Config file | https://mobile-production.content-square.net | See Configuration |
Analytics data (EU) | https://m.csqtrk.net | See Sending data |
Analytics data (US) | https://m-aus1.contentsquare.net | See Sending data |
Session Replay data (EU) | https://ka-aeu1.contentsquare.net | See Session Replay requests |
Session Replay data (US) | https://ka-aus1.contentsquare.net | See Session Replay requests |
Snapshot (EU) | https://s.contentsquare.net | See Snapshot capture |
Snapshot (US) | https://s-aus1.contentsquare.net | See Snapshot capture |
Changelog
Related Links
đ Android SDK Documentation