Singular SDK Integration for Flutter

 

The Singular SDK is available as a plug-in for Flutter. The instructions below show you how to integrate Singular into your Flutter app.

Prerequisites

  • This article assumes you have a functional Flutter app.
  • To initialize the SDK, you need your Singular SDK Key and SDK Secret. You can get them in the Singular platform at "Developer Tools > SDK Integration > SDK Keys".

New: Video Guide

Watch this video for a detailed view of the integration process. We recommend using both the video and the written guide below.

Integrating the Singular Plugin

To add the Singular plugin to your Flutter app, add the following lines to your pubspec.yaml file:

dependencies:
  singular_flutter_sdk: ^1.5.1

Then navigate to your project in the terminal and run the following:

flutter packages get

Additional Steps for Android

Adding Dependencies

For Android apps, you need to add the Singular library to the dependencies list in app/build.gradle, as follows:

dependencies {  
  implementation fileTree(dir: 'libs', include: ['*.jar'])
  implementation 'com.android.support:appcompat-v7:28.0.0'
  //...
}

The Singular SDK requires the Google Mobile Ads API, part of the Google Play Services APIs 17.0.0+. If you've already integrated Google Play Services into your app, the requirement is fulfilled. If you haven't, you can integrate Google Mobile Ads individually, by including the following dependency in your app's build.gradle:

implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1'

If you have disabled transitive dependencies for the Singular SDK, add the following to your app's build.gradle.

implementation 'com.android.installreferrer:installreferrer:2.2'
implementation 'com.google.android.gms:play-services-appset:16.0.2'

Additionally, add the following to support the Samsung Galaxy Store's install referrer if your app is distributed through the Samsung Galaxy Store:

implementation 'store.galaxy.samsung.installreferrer:samsung_galaxystore_install_referrer:4.0.0'

Note: If you are presented with a DuplicateClasses error at build time, you may already have Google play-services, and you can comment out the dependency.

Adding Permissions

Add these permissions under the <manifest> tag in your AndroidManifest.xml file:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="BIND_GET_INSTALL_REFERRER_SERVICE" />
<uses-permission android:name="com.android.vending.CHECK_LICENSE" />

Additionally, add the following to support Samsung Galaxy Store's install referrer if your app is distributed through the Samsung Galaxy Store and targets Android 11 or higher:

<queries>
   <package android:name="com.sec.android.app.samsungapps" />
</queries>

If your app build is targeting Android 12/API level 31 or higher, add permissions to access the Google Advertising ID:

<uses-permission android:name="com.google.android.gms.permission.AD_ID" />

Note: Do not add this permission if you're integrating the Kids SDK.

Caution: If your app has the android.permission.GET_TASKS permission, the app may be initialized before the user actually opens it. This may initialize the Singular SDK and cause discrepancies in install time. To prevent the problem, remove the permission if it's not needed, or move the Singular SDK initialization call somewhere else in the code, ensuring it is called only after the user opens the app for the first time.

Additional Steps for iOS

To use the Singular plugin, you have to add the AdServices framework.

Initializing the Singular SDK

The Singular SDK initialization code should be called every time your app is opened. It is a prerequisite to all Singular attribution functionality, and it also sends a new user session to Singular (sessions are used to calculate user retention).

The initialization code goes in your main app widget (ie. main.dart) - the first one that loads when the app is opened. This widget has to be stateful, and the code has to be added in the widget's initState() method.

  1. First, you have to create a SingularConfig object. The object contains your Singular SDK Key and Secret. 
  2. Optionally, you can add settings to enable various SDK features. See the full list of options.
  3. META Install Referrer Attribution Support

    Required SDK configuration to enable "Meta Install Referrer" attribution:

    1. Provide your Facebook App Id in the Singular Configuration Object.
      // To enable META Install Referrer
      
                config.facebookAppId = "INSERT YOUR FACEBOOK APP ID HERE";
    Where can I find an app's Facebook App ID?

Example:

import 'package:singular_flutter_sdk/singular.dart';
import 'package:singular_flutter_sdk/singular_config.dart';
//...
class MyHomePage extends StatefulWidget { 
  //...
  }
  
  class _MyHomePageState extends State<MyHomePage> {
    //...
    @override 
    void initState() {
      super.initState();
      //...
      SingularConfig config = new SingularConfig('SDK KEY', 'SDK SECRET');
      // Set hashed User ID if available

      config.customUserId = "b642b4217b34b1e8d3bd915fc65c4452";
      
      // For iOS (Remove this if you are not displaying an ATT prompt)!

      config.waitForTrackingAuthorizationWithTimeoutInterval = 300;
      
      // To enable SkAdNetwork Support

      config.skAdNetworkEnabled = true;
      
      // To enable META Install Referrer

      config.facebookAppId = "INSERT YOUR FACEBOOK APP ID HERE";
      
      // (optional) Using Singular Global Properties feature to capture 

      // third party identifiers. The respective SDK(s) must be initialized

      // before the Singular SDK. Example of passing the CleverTapID.

      // var cleverTapId = CleverTapPlugin.getCleverTapID();

      // config.withGlobalProperty("CLEVERTAPID", cleverTapId, true);

              
      Singular.start(config);
  }

Handling ATT Consent (Setting an Initialization Delay)

Displaying an ATT (App Tracking Transparency) Prompt

Starting with iOS 14.5, apps are required to ask for user consent (using the App Tracking Transparency framework) before they can access and share some user data that is helpful for tracking purposes, including the device's IDFA.

Singular highly benefits from having the IDFA to identify devices and perform install attribution (although there are ways to perform attribution without the IDFA). We strongly recommend that you ask for the user's consent to get the IDFA.

Delaying Initialization to Wait for ATT Response

By default, the Singular SDK sends a user session when it's initialized. When a session is sent from a new device, it immediately triggers Singular's attribution process - which is performed based only on the data available to Singular at that point. Therefore, it's essential to ask for consent and retrieve the IDFA before the Singular SDK sends the first session.

To delay the firing of a user session, initialize the Singular SDK with the waitForTrackingAuthorizationWithTimeoutInterval option in the Config object. This option is already included in the code sample above.

When using Flutter, you will need to rely on a 3rd-Party package to implement App Tracking Transparency. For example: the app_tracking_transparency plugin to your Flutter

Tip: When you set an initialization delay, the app flow is as follows:

  1. When the app opens, the Singular SDK starts recording a session and user events but does not send them to the Singular server yet.
  2. When App Tracking Transparency consent is granted/denied, or the set time elapses, the SDK sends the session and any queued events to the Singular server (with or without the IDFA).
  3. Singular then starts the attribution process, taking advantage of the IDFA if it is available.
Learn About All Possible ATT Scenarios

The following table summarizes the possible scenarios using this integration:

Scenario IDFA Availability
The user sees the consent dialog and grants consent before the set time elapses. IDFA is available
The user sees the consent dialog and denies consent before the set time elapses. IDFA is not available
The set time expires, then the user is shown the consent dialog and grants consent. IDFA is available only for the user events that are reported after the consent is granted
The set time expires, then the user is shown the consent dialog and denies consent. IDFA is not available
The user is shown the consent dialog, exits the app without taking action, and later opens the app and grants consent after the set time has expired. Any queued events are sent to the Singular server when the app is reopened. The IDFA is not available for these events. Any events tracked after consent is granted do have IDFA associated with them.
The user is shown the consent dialog, exits the app without taking action, and later opens the app and denies consent. Any queued events are sent to the Singular servers when the app is reopened. The IDFA is not available for these events or any of the events tracked afterward.

Sending the User ID to Singular (Optional)

You may send your internal User ID to Singular using a Singular SDK method.

Note: If you use Singular's Cross-Device solution, you must collect the User ID across all platforms.

  • The User ID can be any identifier and should not expose PII (Personally Identifiable Information). For example, you should not use a User's email address, username, or phone number. Singular recommends using a hashed value unique only to your first-party data.
  • The User ID value passed to Singular should also be the same internal User ID you capture across all platforms (Web/Mobile/PC/Console/Offline).
  • Singular will include the User ID in user-level exports, ETL, and Internal BI postbacks (if configured). The User ID is first-party data, and Singular does not share it with other parties.
  • The User ID value, when set with the Singular SDK Method, will persist until it is unset using the unsetCustomUserId method or until the app is uninstalled. Closing or restarting the app does not unset the User ID.

To set the User ID, use the setCustomUserId method. To unset it (for example, if the User "logs out" of the account), call unsetCustomUserId.

Note: If multiple Users use a single device, we recommend implementing a logout flow to set and unset the User ID for each login and logout.

If you already know the user ID when the app opens, call setCustomUserId before initializing the Singular SDK. This way, Singular can have the User ID from the first Session. However, the User ID is typically unavailable until the User registers or performs a login. In that case, call setCustomUserId after the registration flow is complete.

Singular.setCustomUserID Method
Description Send the user ID to Singular.
Signature static void setCustomUserId(String customUserId)
Usage Example
Singular.setCustomUserId("custom_user_id");
Singular.unsetCustomUserID Method
Description Unset the user ID that has been sent to Singular.
Signature static void unsetCustomUserId()
Usage Example
Singular.unsetCustomUserId();

Optional: Custom User ID Device Mapping

Important: This advanced Enterprise feature is only available in exceptional cases. Please consult with one of Singular’s Solution Engineers before implementing it.

Singular can receive additional mobile event tracking data via a server-to-server integration. To utilize this feature, you must map the User ID to Singular’s Mobile Device tracking identifier.

Note: Call this method as soon as possible after initializing the Singular SDK or once you have the User ID.

Singular.setDeviceCustomUserId Method
Description Sets the Custom User Id the same as login and maps it to Singular’s tracking identifier.
Signature static void setDeviceCustomUserId(String customUserId)
Usage Example
Singular.setDeviceCustomUserId("custom_user_id");

Implementing Deep Links

Deep links are links that open the app on the user's phone and send the user directly to a specific page or user experience, instead of just the app's main widget. Deep links are usually used in retargeting campaigns, aimed at users who already have the app on their phone but may not have engaged with it for a while. Singular supports deep linking through Singular Links.

Enabling Singular Links

To enable Singular Links in iOS and in Android, see Singular Links Prerequisites.

For Android support add the following code in the project's MainActivity.java file: 

Java Kotlin
import com.singular.flutter_sdk.SingularBridge;
import android.content.Intent;

@Override
protected void onNewIntent(@NonNull Intent intent) {
  super.onNewIntent(intent);
  SingularBridge.onNewIntent(intent);
}

For iOS Support, in the project’s AppDelegate.m, add the following:

 

Objective-C Swift
// Top of AppDelegate.m

            
#import "SingularAppDelegate.h"

- (BOOL)application:(UIApplication *)application 
     didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
       [GeneratedPluginRegistrant registerWithRegistry:self];
       [SingularAppDelegate shared].launchOptions = launchOptions;
       return [super application:application 
         didFinishLaunchingWithOptions:launchOptions];
}

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

- (BOOL)application:(UIApplication *)app 
     openURL:(NSURL *)url 
     options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
       [[SingularAppDelegate shared] handleOpenUrl:url options:options];
       return [super application:app openURL:url options: options];
}

Handling Singular Links

Use Singular's handler mechanism to read the details of the tracking link that led to the app being opened.

For example:

SingularConfig config = new SingularConfig('<SDK KEY>', '<SDK SECRET>');

config.singularLinksHandler = (SingularLinkParams params) {
  String deeplink = params.deeplink;
  String passthrough = params.passthrough;
  bool isDeferred = params.isDeferred;
  // Add your code here to handle the deep link

};
          
Singular.init(config);

Tracking Events (Non Revenue)

Singular can collect data about in-app events to help analyze the performance of your campaigns and measure KPIs. For example, your organization may want to collect data about user logins, registrations, tutorial completions, or leveling up in a gaming app.

Singular supports a variety of standard events. These commonly used events are often supported by ad networks for reporting and optimization. Another advantage is that when you use standard event names, Singular recognizes them automatically and adds them to the Events list without you having to define them manually. We recommend using standard events whenever possible.

The list of events sent to Singular (with the accompanying attributes) should be compiled by the UA/marketing/business team based on your organization's marketing KPIs. The business team can follow the guide at How to Track In-App Events: Guide For Singular Attribution Customers.

With each event you track, you can pass various attributes. See the recommended standard attributes per event.

In your code, send events to Singular using the event or eventWithArgs methods.

Note: For standard events, use the event's Flutter name as it appears in the Flutter SDK List of Standard Events and Attributes, e.g., sngLogin.

For custom events, events that your organization wants to measure that do not match any of Singular's standard events, use any custom name (maximum of 32 characters). We recommend using names in English for compatibility with any ad network partners that may receive the event from Singular for optimization purposes.

Example:

Singular.event(Events.sngLogin);
Singular.eventWithArgs(eventName, {attributeName:attributeValue});
Map<String, Object> map = HashMap<String, Object>();
map ['name'] = 'John Doe';
map ['age'] = 30;
map ['isStudent'] = false;
Singular.eventWithArgs('event_Name', map);

Tracking Revenue

Sending IAP Events

To let Singular track how much revenue your app is making, send IAP events to Singular. By sending the IAP event you also allow Singular to check the event verification data and make sure it's not fraudulent.

See the following example.

Note: This code snippet requires the Flutter IAP package at https://pub.dev/packages/in_app_purchase.

import 'package:singular_flutter_sdk/singular_iap.dart';
import 'dart:io' show Platform;
            
if (Platform.isIOS) {
  singularPurchase = new SingularIOSIAP(
    product.rawPrice.toStringAsFixed(2),
    product.currencyCode,
    purchase.productID,
    purchase.purchaseID,
    purchase.verificationData.serverVerificationData
  );
}
            
else if (Platform.isAndroid) {
  singularPurchase = new SingularAndroidIAP(
    product.rawPrice.toStringAsFixed(2),
    product.currencyCode,
    purchase.verificationData.serverVerificationData,
    purchase.verificationData.localVerificationData
  );
}
            
Singular.inAppPurchase(eventName, singularPurchase);

Note: Pass currency as a three-letter ISO 4217 currency code, e.g., "USD," "EUR", "INR".

Alternative Method: Sending Custom Revenue Events

Singular also offers the option of reporting revenue by just sending a custom revenue event with a name and a revenue amount. Note that this method does not share the purchase receipt with Singular and therefore, does not allow Singular to verify that it's a legitimate event.

For example:

Singular.customRevenue("MyCustomRevenue", "USD", 5.50);
Map<String, Object> map = HashMap<String, Object>();
map ['name'] = 'John Doe';
map ['age'] = 30;
map ['isStudent'] =false;
Singular.customRevenueWithAttributes('MyCustomRevenue','USD', 20, map);

Note: Pass currency as a three-letter ISO 4217 currency code, e.g., "USD," "EUR", "INR".

Hybrid Event Tracking (Advanced)

Singular recommends sending all events and revenue through the Singular SDK integrated into your app. However, Singular can collect events and revenue from other sources.

Any event NOT sent from the Singular SDK must comply with Singular's Server-to-Server Event documentation requirements and provide the matching device identifier to correctly attribute an event.

Important:

Discrepancies will occur if device identifiers used on Server-to-Server event requests do not have a matching device identifier in Singular. Be aware of the following possibilities:

  • If an event request is received "before" the Singular SDK has recorded the device identifier, from an App Session, then the event request will be considered the "first session" for the unknown device, and Singular will attribute the device as an organic attribution.
  • If the Singular SDK did record a device identifier, but the Singular SDK identifier differs from the device identifier specified in the Server-to-Server Event request then the event will be attributed incorrectly.

Hybrid Event Tracking Guides

Sending Events from an Internal Server

Singular can collect data about revenue from your Server to help analyze the performance and ROI of your campaigns.

Requirements:

  • From an in-app Registration or Login Event, capture and pass the device identifiers and store this data with the User ID on your server. Because device identifiers may change for a user, be sure to update the identifiers when a user generates an app session. This guarantees the server-side event will be attributed to the correct device.
  • Server-side events are platform specific and should only be sent with the device identifier matching the device platform (e.g., IDFA or IDFV for iOS devices).
  • You can use the Singular Internal BI postback mechanism to push an event in real time to your internal endpoint so that you can update the data set on the server side. See the Internal BI Postback FAQ.
  • Review the "Tracking Revenue" section in the Server-to-Server Integration guide for details.
Sending Events from a Revenue Provider
Third-party providers like RevenueCat or adapty can provide Purchase and Subscription Revenue to Singular.

Follow the links below for details on how to enable these partners.

Sending Events from Segment

To enable Segment to send events to Singular, in parallel with the Singular SDK, you must add a "Cloud-Mode" Destination in Segment. Follow our guide HERE.

Adding Ad Revenue Attribution Support

Singular is integrated with mediation platforms such as Google AdMob, AppLovin, Unity LevelPlay (IronSource), and TradPlus for ad revenue attribution. Singular also supports other mediation platforms through our generic ad revenue SDK integration.

You can get ad revenue attribution data from your mediation platform by adding a code snippet to your Singular SDK integration. This also allows you to get ad revenue data for SKAdNetwork campaigns.

Getting user-level ad revenue from your mediation platform enables Singular to send attributed ad revenue back to media sources that can accept this data to run AdROAS campaigns.

See instructions and code snippets for SDK Implementation [HERE].

Adding SKAdNetwork Support

To enable SKAdNetwork tracking for your app, enable the skAdNetworkEnabled configuration option before initializing Singular.

Managed Mode (Recommended)

In managed mode, Singular manages the SKAdNetwork conversion value for you automatically, based on a conversion model of your choice that you can set up in the Singular platform. 

To learn more, see Understanding Singular's Conversion Value Management and the SKAdNetwork Model Configuration FAQ. For a step-by-step guide to using SKAdNetwork with Singular, see How to Get Started with SKAdNetwork.

Note: The SKAN Managed mode is already enabled in the Initialization code snippet above. Make sure that you have these configuration items set.

To enable SKAdNetwork in managed mode, use the following code:

SingularConfig config = new SingularConfig('<SDK KEY>', '<SDK SECRET>');
config.skAdNetworkEnabled = true;
config.waitForTrackingAuthorizationWithTimeoutInterval = 300;
Singular.init(config);

Manual Mode

If you already have your own strategy and tools for managing the SKAdNetwork conversion value, you can enable SKAdNetwork in manual mode.

SingularConfig config = new SingularConfig('SDK KEY', 'SDK SECRET');
config.skAdNetworkEnabled = true;
config.manualSkanConversionManagement = true;
config.waitForTrackingAuthorizationWithTimeoutInterval = 300;
Singular.init(config);

Then, to update the conversion value, use the following code:

ingular.skanUpdateConversionValue(conversionValue)

To track when the conversion value changes, use the following callback function:

config.conversionValueUpdatedCallback = (int conversionValue) {
  print('Received conversionValueUpdatedCallback: ' + conversionValue.toString());
};

To retrieve the current conversion value, use the following code:

Singular.skanGetConversionValue().then((conversionValue) {
  print('conversion value: ' + conversionValue.toString());
});

Other Options

Tracking Uninstalls

To let Singular track app uninstalls, give Singular the APNS/FCM token, as in the following example:

// iOS

Singular.registerDeviceTokenForUninstall(apnsToken);
            
// Android

Singular.registerDeviceTokenForUninstall(fcmToken);

Complying with Data Privacy Laws

Singular provides privacy-safeguarding functionality to help you cooperate with any partners who may be complying with consumer privacy laws such as GDPR and CCPA (California Consumer Privacy Act). These partners want to be notified if the end-user has consented to share their private information.

If you have implemented a way to ask users for consent to share their information, use the limitDataSharing method to notify Singular of the user's choice:

Use Singular.limitDataSharing(false) to indicate that the user consented (opted in) to share their information.

Use Singular.limitDataSharing(true) if the user did not consent.

Singular uses LimitDataSharing in "User Privacy Postbacks" as well as passing this information on to partners who require it in order to comply with relevant regulations. See "User Privacy and Limit Data Sharing" for more information.

Note: The use of the method is optional, but there may be attribution information that the partner will share with Singular only if specifically notified that the user has opted in.

Singular.limitDataSharing Method
Signature Singular.limitDataSharing(boolean shouldLimitDataSharing)
Description Notify Singular of user consent (opt-in) for sharing private data. The Limit Data Sharing method gives you an option to control whether your app sends user data to third parties. This is useful if you want to restrict data sharing based on user preferences or privacy requirements.
Usage Example
// User has opted into sharing data

Singular.limitDataSharing(false);

Additional Methods for GDPR Compliance

The Singular SDK provides several methods to help you comply with GDPR policies and let Singular know about user consent or non-consent for tracking.

Singular.trackingOptIn Method
Description Notify Singular of user consent (opt-in) for tracking. The TrackingOptIn() method is used to send a "gdpr" event to Singular's servers. If you don't call this method, the app will continue tracking users as if they have given consent, but it won't specifically mark them as GDPR opt-in. If your app needs to comply with GDPR (General Data Protection Regulation), you should call this function to ensure that user consent is properly recorded.
Usage Example
Singular.trackingOptIn();
Singular.stopAllTracking Method
Description

Stop all tracking activities for this user on this app.

Note: Calling this method effectively disables the SDK, even after the app restarts (the state is persistent)! The only way to re-enable tracking is by calling resumeAllTracking().
Usage Example
Singular.stopAllTracking();
Singular.resumeAllTracking Method
Description Resume tracking for this user on this app.
Usage Example
Singular.resumeAllTracking();
Singular.isAllTrackingStopped Method
Description Check the tracking status for this user on this app. Returns true if tracking has been stopped using StopAllTracking() and not resumed.
Usage Example
Singular.isAllTrackingStopped();