Flutter SDK - Data Privacy

Complying with Data Privacy Laws

Implement privacy-compliant data collection by notifying Singular of user consent choices for GDPR, CCPA, COPPA, and other consumer privacy regulations.

When users consent or decline to share their information with third parties, use Singular's privacy methods to communicate their choice. This ensures compliance with regulations like California Consumer Privacy Act (CCPA) and enables partners to respect user privacy preferences.

Learn More: See User Privacy and Limit Data Sharing for detailed information on how Singular processes privacy consent.


Limit Data Sharing

Control Third-Party Data Sharing

Notify Singular whether users have consented to share their personal data with third-party partners using the limitDataSharing() method.

Method Signature:

static void limitDataSharing(bool shouldLimitDataSharing)

Parameters:

  • false: User has opted in and consented to share their data
  • true: User has opted out and does not consent to share their data

Important: While optional, this method affects attribution data sharing. Some partners only share complete attribution information when explicitly notified that users have opted in.

For complete method documentation, see limitDataSharing reference.


Usage Examples

Implement data sharing controls based on user privacy preferences.

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

// User has opted in to share their data
void onUserOptedInToDataSharing() {
  Singular.limitDataSharing(false);
  print('Data sharing enabled');
}

// User has opted out and declined to share their data
void onUserOptedOutOfDataSharing() {
  Singular.limitDataSharing(true);
  print('Data sharing limited');
}

// Set based on user preference
void handlePrivacyConsent(bool userConsented) {
  // Pass inverse: false = opted in, true = opted out
  Singular.limitDataSharing(!userConsented);

  print('Data sharing: ${userConsented ? 'Enabled' : 'Limited'}');
}

How It Works:

Singular uses this setting in User Privacy Postbacks and passes it to partners who require it for regulatory compliance.


GDPR Compliance Methods

Manage user tracking consent and control SDK functionality to comply with GDPR (General Data Protection Regulation) and other privacy regulations.

Tracking Consent Management

TrackingOptIn

Record explicit user consent for tracking by sending a GDPR opt-in event to Singular servers.

Method Signature:

static void trackingOptIn()

When to Use:

  • GDPR Compliance: Call when users explicitly consent to tracking in GDPR-regulated regions
  • Consent Recording: Marks users as having provided GDPR consent in Singular's systems
  • Default Behavior: Without this call, SDK continues tracking but doesn't specifically record consent

For complete method documentation, see trackingOptIn reference.


Implementation Example

Call trackingOptIn() after users accept tracking consent through your app's consent dialog.

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

class GDPRManager extends StatefulWidget {
  @override
  _GDPRManagerState createState() => _GDPRManagerState();
}

class _GDPRManagerState extends State<GDPRManager> {
  bool? hasConsent;

  @override
  void initState() {
    super.initState();
    checkStoredConsent();
  }

  Future<void> checkStoredConsent() async {
    final prefs = await SharedPreferences.getInstance();
    final consent = prefs.getString('gdpr_consent');

    if (consent == null) {
      // Show consent dialog
      showGDPRConsentDialog();
    } else {
      setState(() {
        hasConsent = consent == 'true';
      });
    }
  }

  void showGDPRConsentDialog() {
    showDialog(
      context: context,
      barrierDismissible: false,
      builder: (context) => AlertDialog(
        title: Text('Privacy Consent'),
        content: Text(
          'We would like your permission to track app usage and improve your experience.'
        ),
        actions: [
          TextButton(
            onPressed: () {
              Navigator.of(context).pop();
              setState(() {
                hasConsent = false;
              });
            },
            child: Text('Decline'),
          ),
          ElevatedButton(
            onPressed: () async {
              Navigator.of(context).pop();
              await onUserAcceptedTracking();
            },
            child: Text('Accept'),
          ),
        ],
      ),
    );
  }

  Future<void> onUserAcceptedTracking() async {
    // Record user consent
    Singular.trackingOptIn();
    print('User opted in to tracking');

    // Save preference
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('gdpr_consent', 'true');

    setState(() {
      hasConsent = true;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: hasConsent != null
            ? Text('Privacy Status: ${hasConsent! ? 'Opted In' : 'Declined'}')
            : CircularProgressIndicator(),
      ),
    );
  }
}

Tracking Control Methods

StopAllTracking

Completely disable all SDK tracking activities for the current user on this device.

Method Signature:

static void stopAllTracking()

Critical Warning: This method permanently disables the SDK until resumeAllTracking() is called. The disabled state persists across app restarts and can only be reversed programmatically.

Behavior:

  • Immediate Effect: Stops all tracking, event reporting, and data collection instantly
  • Persistent State: Remains disabled even after app closes and reopens
  • No Automatic Reset: Must explicitly call resumeAllTracking() to re-enable

For complete method documentation, see stopAllTracking reference.


Implementation Example

Stop tracking when users decline consent or opt out through privacy settings.

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

// User declined all tracking
Future<void> onUserDeclinedTracking() async {
  Singular.stopAllTracking();
  print('All tracking stopped');

  // Store preference
  final prefs = await SharedPreferences.getInstance();
  await prefs.setBool('tracking_enabled', false);
}

// Handle user opt-out from settings menu
void handlePrivacySettingsChange(bool trackingEnabled) {
  if (!trackingEnabled) {
    Singular.stopAllTracking();
    print('Privacy settings: Tracking disabled');
  }
}

ResumeAllTracking

Re-enable tracking after it was stopped with stopAllTracking().

Method Signature:

static void resumeAllTracking()

Use Cases:

  • Consent Change: User changes privacy preferences and opts back into tracking
  • Privacy Settings: User updates consent through app settings menu
  • Regional Compliance: Re-enable tracking when user moves to non-regulated regions

For complete method documentation, see resumeAllTracking reference.


Implementation Example

Resume tracking when users opt back in or update their privacy preferences.

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

// User opted back in to tracking
Future<void> onUserResumedTracking() async {
  Singular.resumeAllTracking();
  print('Tracking resumed');

  // Update stored preference
  final prefs = await SharedPreferences.getInstance();
  await prefs.setBool('tracking_enabled', true);
}

// Handle consent update from settings
void handlePrivacySettingsChange(bool trackingEnabled) {
  if (trackingEnabled) {
    Singular.resumeAllTracking();
    print('Privacy settings: Tracking enabled');
  }
}

IsAllTrackingStopped

Check whether tracking has been disabled for the current user.

Method Signature:

static Future<bool> isAllTrackingStopped()

Returns:

  • Future<true>: Tracking is currently stopped via stopAllTracking()
  • Future<false>: Tracking is active (either never stopped or resumed)

For complete method documentation, see isAllTrackingStopped reference.


Implementation Example

Check tracking status to sync UI state with SDK tracking state.

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

class PrivacySettingsUI extends StatefulWidget {
  @override
  _PrivacySettingsUIState createState() => _PrivacySettingsUIState();
}

class _PrivacySettingsUIState extends State<PrivacySettingsUI> {
  bool trackingEnabled = false;
  String statusText = 'Checking...';

  @override
  void initState() {
    super.initState();
    updatePrivacyUI();
  }

  // Check current tracking status
  Future<bool> isTrackingEnabled() async {
    final isStopped = await Singular.isAllTrackingStopped();
    return !isStopped;
  }

  // Display privacy status in settings
  Future<String> getPrivacyStatusText() async {
    final isStopped = await Singular.isAllTrackingStopped();
    return isStopped ? 'Tracking: Disabled' : 'Tracking: Enabled';
  }

  // Sync UI with tracking state
  Future<void> updatePrivacyUI() async {
    final isStopped = await Singular.isAllTrackingStopped();
    final status = await getPrivacyStatusText();

    setState(() {
      trackingEnabled = !isStopped;
      statusText = status;
    });

    print('Current tracking state: ${isStopped ? 'Stopped' : 'Active'}');
  }

  // Handle toggle change from UI
  Future<void> onTrackingToggleChanged(bool enabled) async {
    if (enabled) {
      Singular.resumeAllTracking();
    } else {
      Singular.stopAllTracking();
    }

    await updatePrivacyUI();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(statusText),
        Switch(
          value: trackingEnabled,
          onChanged: onTrackingToggleChanged,
        ),
      ],
    );
  }
}

Children's Privacy Protection

TrackingUnder13

Notify Singular that the user is under 13 years old to comply with COPPA (Children's Online Privacy Protection Act) and other child privacy regulations.

Method Signature:

static void trackingUnder13()

Compliance Requirements:

  • COPPA Compliance: Required for apps that collect data from children under 13 in the United States
  • Age-Gated Content: Use when users identify themselves as under 13 during registration or age verification
  • Restricted Tracking: Limits data collection to comply with children's privacy protection laws

For complete method documentation, see trackingUnder13 reference.


Implementation Example

Call trackingUnder13() immediately after determining the user is under 13 through age verification.

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';

class COPPAManager extends StatefulWidget {
  @override
  _COPPAManagerState createState() => _COPPAManagerState();
}

class _COPPAManagerState extends State<COPPAManager> {
  final TextEditingController ageController = TextEditingController();

  // User identified as under 13
  Future<void> onUserUnder13() async {
    Singular.trackingUnder13();
    print('COPPA mode enabled for user under 13');

    // Store age category for app restart
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('user_age_category', 'under_13');
  }

  // Call after age verification
  Future<void> onAgeVerified(int userAge) async {
    final prefs = await SharedPreferences.getInstance();

    if (userAge < 13) {
      Singular.trackingUnder13();
      print('COPPA restrictions applied');

      // Also limit advertising identifiers
      Singular.limitDataSharing(true);

      await prefs.setString('user_age_category', 'under_13');

      showDialog(
        context: context,
        builder: (context) => AlertDialog(
          title: Text('Child Account'),
          content: Text('Special privacy protections have been applied.'),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: Text('OK'),
            ),
          ],
        ),
      );
    } else {
      await prefs.setString('user_age_category', 'adult');
      print('Adult account - standard tracking');
    }
  }

  void handleAgeSubmit() {
    final userAge = int.tryParse(ageController.text);

    if (userAge == null || userAge < 1 || userAge > 120) {
      showDialog(
        context: context,
        builder: (context) => AlertDialog(
          title: Text('Invalid Age'),
          content: Text('Please enter a valid age.'),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: Text('OK'),
            ),
          ],
        ),
      );
      return;
    }

    onAgeVerified(userAge);
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TextField(
          controller: ageController,
          decoration: InputDecoration(
            hintText: 'Enter your age',
          ),
          keyboardType: TextInputType.number,
        ),
        ElevatedButton(
          onPressed: handleAgeSubmit,
          child: Text('Submit'),
        ),
      ],
    );
  }

  @override
  void dispose() {
    ageController.dispose();
    super.dispose();
  }
}

Important: Call this method as early as possible after determining the user is under 13, ideally during app initialization or immediately after age verification. This ensures all subsequent tracking respects children's privacy regulations.


LimitAdvertisingIdentifiers Configuration

Restrict the collection and use of advertising identifiers (GAID on Android, IDFA on iOS) during SDK initialization for apps serving children.

Configuration Property:

bool? limitAdvertisingIdentifiers

Parameters:

  • true: Enable limited advertising identifiers mode (restrict collection)
  • false: Disable limited advertising identifiers mode (normal collection)

Use Cases:

  • Children-Focused Apps: Apps designed primarily for users under 13
  • Mixed Audience Apps: Apps that serve both adults and children where age is determined before SDK initialization
  • Privacy-First Apps: Apply advertising identifier limitations from the start

For complete configuration documentation, see limitAdvertisingIdentifiers reference.


Configuration Example

Set advertising identifier limitations during SDK initialization for apps that know privacy requirements upfront.

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

// Limit advertising identifiers at initialization
void initializeSingular() {
  final config = SingularConfig(
    'YOUR_SDK_KEY',
    'YOUR_SDK_SECRET'
  );

  // Enable limited advertising identifiers mode
  config.limitAdvertisingIdentifiers = true;

  Singular.start(config);
  print('SDK initialized with advertising identifier restrictions');
}

Combined Approach: For apps serving children, combine trackingUnder13() with limitAdvertisingIdentifiers = true to ensure comprehensive COPPA compliance.


Implementation Best Practices

Complete Privacy Management Example

Implement comprehensive privacy controls that respect user preferences and comply with regulations.

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';

const String PREF_USER_CONSENT = 'privacy_user_consent';
const String PREF_DATA_SHARING = 'privacy_data_sharing';
const String PREF_AGE_CATEGORY = 'user_age_category';

/// Initialize privacy settings on app startup based on stored preferences
Future<void> initializePrivacySettings() async {
  final hasUserConsent = await getUserConsent();
  final allowDataSharing = await getDataSharingPreference();
  final ageCategory = await getAgeCategory();

  // Apply age-based restrictions first
  if (ageCategory == 'under_13') {
    Singular.trackingUnder13();
    print('COPPA restrictions applied on startup');
  }

  // Apply stored tracking preference
  if (hasUserConsent) {
    Singular.trackingOptIn();
    Singular.resumeAllTracking();
    print('Privacy initialized: Tracking enabled with consent');
  } else {
    Singular.stopAllTracking();
    print('Privacy initialized: Tracking disabled');
  }

  // Set data sharing preference (inverse logic)
  Singular.limitDataSharing(!allowDataSharing);

  print('Privacy initialized: consent=$hasUserConsent, sharing=$allowDataSharing');
}

/// User accepts tracking via consent dialog
Future<void> onUserAcceptedTracking() async {
  await saveUserConsent(true);

  Singular.trackingOptIn();
  Singular.resumeAllTracking();

  print('User accepted tracking');
}

/// User declines tracking
Future<void> onUserDeclinedTracking() async {
  await saveUserConsent(false);

  Singular.stopAllTracking();

  print('User declined tracking');
}

/// User updates data sharing preference
Future<void> setDataSharingEnabled(bool enabled) async {
  await saveDataSharingPreference(enabled);

  // Note: limitDataSharing uses inverse logic
  // false = data sharing enabled, true = data sharing limited
  Singular.limitDataSharing(!enabled);

  print('Data sharing: ${enabled ? 'Enabled' : 'Limited'}');
}

/// Check if tracking is currently enabled
Future<bool> isTrackingEnabled() async {
  final isStopped = await Singular.isAllTrackingStopped();
  return !isStopped;
}

/// Get current privacy status as readable text
Future<String> getPrivacyStatus() async {
  final isEnabled = await isTrackingEnabled();
  final dataSharingEnabled = await getDataSharingPreference();
  final ageCategory = await getAgeCategory();

  String status = 'Tracking: ${isEnabled ? 'Enabled' : 'Disabled'}
';
  status += 'Data Sharing: ${dataSharingEnabled ? 'Enabled' : 'Limited'}';

  if (ageCategory == 'under_13') {
    status += '
COPPA: Restrictions Applied';
  }

  return status;
}

// Private helper methods for SharedPreferences

Future<bool> getUserConsent() async {
  final prefs = await SharedPreferences.getInstance();
  return prefs.getBool(PREF_USER_CONSENT) ?? false;
}

Future<void> saveUserConsent(bool consent) async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setBool(PREF_USER_CONSENT, consent);
}

Future<bool> getDataSharingPreference() async {
  final prefs = await SharedPreferences.getInstance();
  return prefs.getBool(PREF_DATA_SHARING) ?? false;
}

Future<void> saveDataSharingPreference(bool enabled) async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setBool(PREF_DATA_SHARING, enabled);
}

Future<String?> getAgeCategory() async {
  final prefs = await SharedPreferences.getInstance();
  return prefs.getString(PREF_AGE_CATEGORY);
}

// Flutter widget to initialize privacy on app startup
class PrivacyInitializer extends StatefulWidget {
  final Widget child;

  const PrivacyInitializer({Key? key, required this.child}) : super(key: key);

  @override
  _PrivacyInitializerState createState() => _PrivacyInitializerState();
}

class _PrivacyInitializerState extends State<PrivacyInitializer> {
  @override
  void initState() {
    super.initState();
    initializePrivacySettings();
  }

  @override
  Widget build(BuildContext context) {
    return widget.child;
  }
}

Key Implementation Guidelines

Best Practices:

  • Persistent Storage: Save user preferences using SharedPreferences or secure storage solution
  • Early Initialization: Apply privacy settings before SDK initialization when possible
  • UI Sync: Keep settings UI synchronized with actual SDK state using isAllTrackingStopped()
  • Clear Communication: Provide clear, accessible privacy controls in app settings
  • Inverse Logic: Remember that limitDataSharing(false) means data sharing is enabled, while true means it's limited
  • COPPA Priority: Apply children's privacy protections (trackingUnder13()) before other privacy settings
  • Compliance Documentation: Maintain records of when and how users provide or revoke consent