Contentsquare Flutter Integration

Last updated on

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:

📚 Android documentation

📚 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:

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
  2. Call the SDK when the app is launched via a deeplink

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
  1. Open your project settings
  2. Select the app target
  3. Select the Info settings
  4. Scroll to URL Types
  5. Set the URL scheme to cs-$(PRODUCT_BUNDLE_IDENTIFIER)
[tab] Text editor
  1. Open the Info.plist of your project

  2. 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]

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.

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:
StateScreen name
No trip plannedHome - no trip
Trip plannedHome - trip planned
Trip about to startHome - upcoming trip
Trip in progressHome - trip in progress
  • Product detail screen of an e-commerce app with different layouts depending on the type of product:
StateScreen name
Default templateProduct detail
Template with suggested productsProduct detail - Suggestions
Template with bundled productsProduct 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, 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:

  1. tracker.registerWebViewController
  2. tracker.javascriptChannels: Register these channels which are used to communicate between Contentsquare and your WebView
  3. tracker.initializeWebViewTracking: This must be called after registerWebViewController and when the javascriptChannels 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 VersioniOS SDK VersionAndroid SDK VersionMinimum Flutter Version
1.3.24.18.04.15.02.8.0
1.3.1 (deprecated)4.18.04.15.02.8.0
1.3.04.17.04.14.02.8.0
1.2.04.13.04.13.12.8.0
1.1.04.13.04.13.12.8.0
1.0.14.13.04.13.12.0.0
1.0.04.13.04.11.02.0.0
0.5.34.13.04.11.02.0.0
0.5.24.13.04.9.02.0.0
0.5.14.13.04.9.02.0.0
0.5.04.13.04.9.02.0.0
0.4.24.12.04.8.02.0.0
0.4.14.11.04.8.02.0.0
0.4.04.11.04.7.02.0.0
0.3.04.11.04.7.02.0.0
0.2.34.10.14.7.02.0.0
0.2.24.10.04.3.02.0.0
0.2.14.6.04.3.02.0.0
0.2.04.6.04.3.02.0.0
0.1.04.3.04.1.02.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):

RequestEndpointDetail
Config filehttps://mobile-production.content-square.netSee Configuration
Analytics data (EU)https://m.csqtrk.netSee Sending data
Analytics data (US)https://m-aus1.contentsquare.netSee Sending data
Session Replay data (EU)https://ka-aeu1.contentsquare.netSee Session Replay requests
Session Replay data (US)https://ka-aus1.contentsquare.netSee Session Replay requests
Snapshot (EU)https://s.contentsquare.netSee Snapshot capture
Snapshot (US)https://s-aus1.contentsquare.netSee Snapshot capture

Changelog

📚 Flutter plugin Changelog

📚 Android SDK Documentation

📚 iOS SDK Documentation