Flutter SDK - Setting a User ID and Hashed User Details

Setting a User ID and Hashed User Details

Send your internal user ID to Singular to enable cross-device tracking and user-level data reporting.

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

User ID Requirements

Privacy and Best Practices

Follow these guidelines when implementing user ID tracking to ensure privacy compliance and proper cross-device measurement.

  • No PII: The User ID should not expose Personally Identifiable Information (PII) such as email addresses, usernames, or phone numbers. Use a hashed value unique to your first-party data.
  • Consistency Across Platforms: The User ID value must be the same internal identifier you capture across all platforms (Web/Mobile/PC/Console/Offline) for accurate cross-device measurement.
  • First-Party Data: Singular includes the User ID in user-level exports, ETL, and Internal BI postbacks (if configured). The User ID is first-party data and is not shared with third parties.
  • Persistence: The User ID persists until explicitly unset using unsetCustomUserId() or until the app is uninstalled. Closing or restarting the app does not clear the User ID.

Implementation Overview

When to Set the User ID

Use Singular.setCustomUserId() to set the user identifier and Singular.unsetCustomUserId() to clear it during logout.

Best Practice: If multiple users share a single device, implement a logout flow that calls setCustomUserId() on login and unsetCustomUserId() on logout.

If you already know the user ID when the app opens, configure it using the customUserId property before initializing the Singular SDK. This ensures Singular receives the User ID from the first session. However, the User ID is typically unavailable until the user registers or logs in, in which case call setCustomUserId() after the registration or authentication flow completes.


SDK Methods

Set Custom User ID

Send your internal user ID to Singular for cross-device tracking and user-level reporting.

Dart
import 'package:singular_flutter_sdk/singular.dart';

// Set the user ID after login or registration
Singular.setCustomUserId('user_123456');

Method Signature:

static void setCustomUserId(String customUserId)

Example: Set User ID After Login

Call setCustomUserId() immediately after the user successfully completes authentication to ensure all subsequent events are associated with their user ID.

Dart
import 'package:singular_flutter_sdk/singular.dart';

Future<void> handleUserLogin(String email, String password) async {
  try {
    // Your authentication logic
    final response = await authenticateUser(email, password);

    if (response.success) {
      // Set the user ID in Singular after successful login
      Singular.setCustomUserId(response.userId);

      print('User ID set: ${response.userId}');

      // Navigate to home screen
      navigateToHome();
    }
  } catch (error) {
    print('Login failed: $error');
  }
}

Unset Custom User ID

Clear the user ID when a user logs out to ensure accurate session tracking for multi-user devices.

Dart
import 'package:singular_flutter_sdk/singular.dart';

// Unset the user ID on logout
Singular.unsetCustomUserId();

Method Signature:

static void unsetCustomUserId()

Example: Unset User ID on Logout

Call unsetCustomUserId() during the logout flow to clear the user ID and prevent incorrect attribution of subsequent events.

Dart
import 'package:singular_flutter_sdk/singular.dart';

Future<void> handleUserLogout() async {
  try {
    // Clear app data and user session
    await clearUserSession();

    // Unset the user ID in Singular
    Singular.unsetCustomUserId();

    print('User ID cleared');

    // Navigate to login screen
    navigateToLogin();
  } catch (error) {
    print('Logout failed: $error');
  }
}

Set User ID During Initialization

If the user ID is available when the app launches (e.g., user is already logged in), configure it during SDK initialization using the customUserId property. This ensures the first session includes the user ID.

Dart
import 'package:flutter/material.dart';
import 'package:singular_flutter_sdk/singular.dart';
import 'package:singular_flutter_sdk/singular_config.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
    initializeSingular();
  }

  Future<void> initializeSingular() async {
    // Check if user is already logged in
    final prefs = await SharedPreferences.getInstance();
    final userId = prefs.getString('user_id');

    // Create configuration
    SingularConfig config = SingularConfig(
      'YOUR_SDK_KEY',
      'YOUR_SDK_SECRET'
    );

    // If user ID exists, set it during initialization
    if (userId != null) {
      config.customUserId = userId;
    }

    // Initialize SDK
    Singular.start(config);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

Configuration Property:

String? customUserId

Recommendation: Use the customUserId configuration property during initialization for apps with persistent login sessions. For apps where users must log in each time, call setCustomUserId() after authentication.


Hashed User Details (Email and Phone)

The User ID above is your own opaque identifier. If you also want to send the user's email address or phone number to Singular, use the separate SingularUserDetails API. The SDK normalizes and SHA-256 hashes these values on the device, so the raw email address and phone number are never transmitted.

Availability: Flutter SDK version 1.9.1 and above, which bundles native iOS 12.14.2 and Android 12.16.1.

Choosing a Hashing Mode

There are two ways to supply user details. Pick one per user and stay consistent.

  • SDK-hashed (recommended): Set the cleartext email or phone number. The SDK normalizes the value, hashes it, and generates every variant Singular can match on.
  • Pre-hashed: Set values you have already normalized and SHA-256 hashed yourself. The SDK stores them exactly as given and does no further processing. Use this when your app is not permitted to hold cleartext user details at the point of the call.

Important: If you set both a cleartext value and its matching pre-hashed variant, the pre-hashed value wins.

SingularUserDetails Properties

SingularUserDetails exposes six nullable String properties. Assign the ones you have and leave the rest unset.

Property Details
email

Mode: SDK-hashed

Cleartext email address. The SDK trims whitespace and lowercases the value before hashing. For gmail.com and googlemail.com addresses it also generates a second variant with any +tag suffix and all dots removed from the local part.

Example: user@example.com

phoneNumber

Mode: SDK-hashed

Cleartext phone number. The SDK generates two variants: an E.164 form that keeps a leading + and strips all other non-digits, and a digits-only form that strips the + as well. Include the country code so the E.164 variant is usable.

Example: +15551234567

emailSTD

Mode: Pre-hashed

SHA-256 hash of the email address after trimming and lowercasing.

emailNoDots

Mode: Pre-hashed

SHA-256 hash of the email address after trimming, lowercasing, and removing any +tag suffix and all dots from the local part. Applies to Gmail-style addresses.

phoneE164

Mode: Pre-hashed

SHA-256 hash of the phone number in E.164 form, keeping the leading +.

phoneDigits

Mode: Pre-hashed

SHA-256 hash of the phone number with every non-digit removed, including the leading +.


Set User Details at Initialization

Set user details on your SingularConfig before calling Singular.start so they are attached to the first session the SDK sends. You can assign the userDetails property directly or call withUserDetails; the two are equivalent.

Dart
import 'package:singular_flutter_sdk/singular.dart';
import 'package:singular_flutter_sdk/singular_config.dart';
import 'package:singular_flutter_sdk/singular_user_details.dart';

SingularUserDetails userDetails = SingularUserDetails();
userDetails.email = 'user@example.com';
userDetails.phoneNumber = '+15551234567';

SingularConfig config = SingularConfig('SDK KEY', 'SDK SECRET');
config.withUserDetails(userDetails);

Singular.start(config);

Method Signature:

void withUserDetails(SingularUserDetails userDetails)

Note: withUserDetails returns void, so it cannot be chained onto the SingularConfig constructor. Call it on its own line, or assign config.userDetails directly.


Set User Details After Initialization

If the email address or phone number is only known after login or registration, call Singular.setUserDetails at that point instead. The values are attached to every session and event the SDK sends from then on.

Dart
import 'package:singular_flutter_sdk/singular.dart';
import 'package:singular_flutter_sdk/singular_user_details.dart';

// Cleartext values, hashed by the SDK
SingularUserDetails userDetails = SingularUserDetails();
userDetails.email = 'user@example.com';
userDetails.phoneNumber = '+15551234567';
Singular.setUserDetails(userDetails);

// Or supply your own SHA-256 hashes
SingularUserDetails hashed = SingularUserDetails();
hashed.emailSTD = 'b4c9a289323b21a01c3e940f150eb9b8c542587f1abfd8f0e1cc1ffc5e475514';
hashed.phoneE164 = '8a59780bb8cd2ba022bfa5ba2ea3b6e07af17a7d8b30c1f9b3390e36f69019e4';
Singular.setUserDetails(hashed);

Method Signature:

static void setUserDetails(SingularUserDetails userDetails)

Clear User Details

Stored user details persist on the device across app launches, and are removed when the app is uninstalled. Call Singular.clearUserDetails on logout, or whenever the user withdraws consent, to remove them.

Dart
import 'package:singular_flutter_sdk/singular.dart';

// Remove stored user details on logout
Singular.clearUserDetails();

Method Signature:

static void clearUserDetails()

Three ways to clear stored details. clearUserDetails is not the only one. Calling setUserDetails with a SingularUserDetails object that has no properties set also clears them, and so does calling it with an object whose values are all rejected as invalid — that last case clears the stored payload rather than leaving it untouched. If you mean to leave stored details alone, do not call setUserDetails at all.

Note: Setting userDetails on a later launch does not erase what was stored earlier, so a previously stored payload keeps being sent until you clear it.


Validation and Privacy Behavior

  • Email validation: A cleartext email must contain exactly one @ and a dot after it. Invalid values are rejected and logged, not sent.
  • Phone validation: A cleartext phone number must contain at least 6 digits. Shorter values are rejected and logged.
  • Wrong-mode protection: A value that already looks hashed is rejected by email and phoneNumber. Likewise, the pre-hashed properties reject anything that is not a 64-character SHA-256 hex string.
  • Storage: The hashed payload is kept in the app's own private storage on each platform and is removed when the app is uninstalled. Cleartext values are never stored.
  • Limit Data Sharing: While Limit Data Sharing is enabled, the user details payload is withheld from all requests. See Data Privacy.
  • Consent: Collect and send email addresses and phone numbers only where you have a lawful basis to do so. Hashing does not remove your obligations under GDPR, CCPA, or equivalent regulations.