usercentrics_manager 0.1.0 copy "usercentrics_manager: ^0.1.0" to clipboard
usercentrics_manager: ^0.1.0 copied to clipboard

A unified, cross-platform privacy compliance facade for Flutter apps targeting iOS, Android, and Web, built on top of Usercentrics CMP.

Usercentrics Manager #

A unified, cross-platform privacy compliance facade for Flutter apps targeting iOS, Android, and Web, built on top of Usercentrics CMP.

Write one consent logic that works everywhere β€” no platform-specific branching needed.

pub package License: MIT


πŸ“‹ Table of Contents #


✨ Features #

  • βœ… Unified API: Single UsercentricsManager.instance interface for mobile (via native SDK) and web (via JS injection)
  • βœ… Auto Script Injection (Web): No manual <script> tags β€” the package injects and configures the Usercentrics CMP automatically
  • βœ… Native Performance: Fully wraps the official usercentrics_sdk on mobile for optimal UX
  • βœ… Dynamic Localization: Change the CMP language on the fly using changeLanguage() with 50+ supported languages (requires premium account for multi-language dashboard configuration)
  • βœ… Reactive Consent Stream: Listen to real-time consent changes via consentStream
  • βœ… Cross-Device Sync: Restore user consent across devices using loginUser(uid)
  • βœ… Compliance-Ready: Supports GDPR (Right to Erasure, Right to Access) and CCPA (Do Not Sell)
  • βœ… Safe Fallback: Gracefully degrades on unsupported platforms (e.g., CLI/Dart-only)
  • βœ… Exception Handling: Clear error messages when methods are called before initialization

πŸ› οΈ Installation #

1. Add the dependency #

Add to your pubspec.yaml:

dependencies:
  usercentrics_manager: ^0.1.1  # Use latest version from pub.dev

βœ… No need to manually add usercentrics_sdk or universal_html β€” they're included as transitive dependencies.

Then run:

flutter pub get

πŸ”§ Platform Setup #

πŸ€– Android #

Minimum Requirements:

  • minSdkVersion 21 or higher
  • compileSdkVersion 33 or higher (recommended)

Update android/app/build.gradle:

android {
  defaultConfig {
    minSdkVersion 21
    compileSdkVersion 33
  }
}

ProGuard Rules (if using code obfuscation):

Add to android/app/proguard-rules.pro:

-keep class com.usercentrics.sdk.** { *; }

🍏 iOS #

Minimum Requirements:

  • iOS 11.0 or higher
  • Swift 5.0+

Update ios/Podfile:

platform :ios, '11.0'

Then run:

cd ios && pod install

πŸ“ Note: No Info.plist changes are required unless your app accesses camera, location, etc. (handled separately by iOS).


🌐 Web #

No changes to web/index.html needed!
The package automatically injects the Usercentrics loader script into <head> during initialize().

Content Security Policy (CSP):

Ensure your CSP allows scripts from Usercentrics:

Content-Security-Policy: script-src 'self' https://web.cmp.usercentrics.eu;

If using a meta tag in web/index.html:

<meta http-equiv="Content-Security-Policy" 
      content="script-src 'self' https://web.cmp.usercentrics.eu;">

πŸš€ Quick Start #

1. Initialize at App Startup #

import 'package:flutter/material.dart';
import 'package:usercentrics_manager/usercentrics_manager.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  try {
    await UsercentricsManager.instance.initialize(
      settingsId: 'YOUR_USERCENTRICS_SETTINGS_ID', // From Usercentrics Dashboard
      uid: 'user-123', // Optional: restore cross-device session
      defaultLanguage: UsercentricsLanguage.english, // Optional: set initial language
    );
    
    print('βœ… Usercentrics initialized successfully');
  } catch (e) {
    print('❌ Usercentrics initialization failed: $e');
  }

  runApp(const MyApp());
}
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Privacy Demo')),
        body: Center(
          child: ElevatedButton(
            onPressed: () async {
              // Show banner only if consent is needed
              await UsercentricsManager.instance.showPrivacyBannerIfNeeded();
            },
            child: const Text('Check Privacy Settings'),
          ),
        ),
      ),
    );
  }
}

πŸ“– Detailed Usage #

Use StreamBuilder to react to consent updates in real-time:

class ConsentAwareWidget extends StatelessWidget {
  const ConsentAwareWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<List<PrivacyServiceConsent>>(
      stream: UsercentricsManager.instance.consentStream,
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return const CircularProgressIndicator();
        }

        final consents = snapshot.data!;
        
        // Check specific service consent
        final analyticsConsent = consents.firstWhere(
          (c) => c.templateId == 'YOUR_ANALYTICS_TEMPLATE_ID',
          orElse: () => const PrivacyServiceConsent(
            templateId: '',
            status: false,
            name: '',
          ),
        );

        if (analyticsConsent.status) {
          // Initialize analytics
          print('βœ… Analytics enabled');
        } else {
          print('❌ Analytics disabled');
        }

        return Column(
          children: [
            Text('Total Services: ${consents.length}'),
            Text('Consented Services: ${consents.where((c) => c.status).length}'),
            ...consents.map((c) => ListTile(
              title: Text(c.name),
              trailing: Icon(
                c.status ? Icons.check_circle : Icons.cancel,
                color: c.status ? Colors.green : Colors.red,
              ),
            )),
          ],
        );
      },
    );
  }
}

Managing User Sessions #

// On user login - restore their consent preferences
Future<void> onUserLogin(String userId) async {
  try {
    await UsercentricsManager.instance.loginUser(userId);
    print('βœ… User session restored');
  } catch (e) {
    print('❌ Failed to restore session: $e');
  }
}

// On user logout - clear local consent data
Future<void> onUserLogout() async {
  try {
    await UsercentricsManager.instance.logoutUser();
    print('βœ… User session cleared');
  } catch (e) {
    print('❌ Failed to clear session: $e');
  }
}

Dynamic Language Switching #

class LanguageSelector extends StatelessWidget {
  const LanguageSelector({Key? key}) : super(key: key);

  Future<void> _changeLanguage(UsercentricsLanguage language) async {
    try {
      await UsercentricsManager.instance.changeLanguage(language);
      print('βœ… Language changed to ${language.code}');
    } catch (e) {
      print('❌ Failed to change language: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return DropdownButton<UsercentricsLanguage>(
      items: [
        DropdownMenuItem(
          value: UsercentricsLanguage.english,
          child: Text('English'),
        ),
        DropdownMenuItem(
          value: UsercentricsLanguage.french,
          child: Text('FranΓ§ais'),
        ),
        DropdownMenuItem(
          value: UsercentricsLanguage.german,
          child: Text('Deutsch'),
        ),
        DropdownMenuItem(
          value: UsercentricsLanguage.spanish,
          child: Text('EspaΓ±ol'),
        ),
      ],
      onChanged: (language) {
        if (language != null) _changeLanguage(language);
      },
    );
  }
}

⚠️ Premium Feature: Multi-language support requires a premium Usercentrics account. You must first configure and enable additional languages in your Usercentrics Dashboard before calling changeLanguage().

// Accept specific service
await UsercentricsManager.instance.setConsentStatus(
  'GOOGLE_ANALYTICS_TEMPLATE_ID',
  true, // grant consent
);

// Reject specific service
await UsercentricsManager.instance.setConsentStatus(
  'FACEBOOK_PIXEL_TEMPLATE_ID',
  false, // revoke consent
);

// Accept all services
await UsercentricsManager.instance.setTrackingEnabled(true);

// Reject all services (e.g., "Do Not Sell" toggle)
await UsercentricsManager.instance.setTrackingEnabled(false);

// Check if specific service is consented
bool isAnalyticsEnabled = await UsercentricsManager.instance.getConsentStatus(
  'GOOGLE_ANALYTICS_TEMPLATE_ID',
);

// Check if any tracking is enabled
bool isTracked = await UsercentricsManager.instance.isUserTracked();

GDPR Compliance Operations #

// Right to Erasure (Data Deletion)
Future<void> deleteUserData() async {
  final result = await UsercentricsManager.instance.requestDataDeletion();
  
  if (result.success) {
    print('βœ… ${result.message}');
    // Note: This only clears LOCAL consent data
    // You must implement backend deletion separately
  } else {
    print('❌ ${result.message}');
  }
}

// Right to Access (Data Export)
Future<void> exportUserData() async {
  final result = await UsercentricsManager.instance.requestDataAccess();
  
  if (result.success && result.dataUrl != null) {
    // Open the data URL or download the file
    print('βœ… Data available at: ${result.dataUrl}');
  } else {
    print('ℹ️ ${result.message}');
    // Typically requires backend implementation
  }
}

πŸ“š Complete API Reference #

Core Methods #

Method Returns Description Premium Required?
initialize({required String settingsId, String? uid, UsercentricsLanguage? defaultLanguage}) Future<void> Initializes the SDK (mobile) or injects script (web). Must be called before any other method. ❌ No
consentStream Stream<List<PrivacyServiceConsent>> Stream that emits the current list of service consents. Updates automatically when consent changes. ❌ No
isInitialized bool Returns true if the manager has been successfully initialized. ❌ No
dispose() void Cleans up resources (stream controllers, event listeners). Call when shutting down. ❌ No

UI Display Methods #

Method Returns Description Premium Required?
showPrivacyBanner() Future<void> Displays the first layer consent banner. ❌ No
showPrivacyManager() Future<void> Displays the second layer detailed privacy settings. ❌ No
showPrivacyBannerIfNeeded() Future<void> Automatically shows the banner only if no consent decision has been made yet. ❌ No

Session Management #

Method Returns Description Premium Required?
loginUser(String uid) Future<void> Restores a user's consent session across devices using their unique ID. ⚠️ Yes - Cross-device consent sync
logoutUser() Future<void> Clears the current user session and local consent data. ❌ No
Method Returns Description Premium Required?
setConsentStatus(String serviceId, bool status) Future<void> Grants (true) or revokes (false) consent for a specific service by its template ID. ❌ No
getConsentStatus(String serviceId) Future<bool> Returns the current consent status for a specific service template ID. ❌ No
setTrackingEnabled(bool enabled) Future<void> Accepts all services (true) or denies all services (false). Useful for "Accept All" / "Reject All" buttons. ❌ No
isUserTracked() Future<bool> Returns true if any service has been granted consent, false if all are denied. ❌ No

Localization #

Method Returns Description Premium Required?
changeLanguage(UsercentricsLanguage language) Future<void> Dynamically changes the CMP UI language. Supports 50+ languages via the UsercentricsLanguage enum. Note: Languages must be configured in the Usercentrics Dashboard first. ⚠️ Yes - Multi-language support requires premium

GDPR/CCPA Compliance #

Method Returns Description Premium Required?
requestDataDeletion() Future<DataDeletionResult> Implements the Right to Erasure by clearing local consent data. Backend deletion must be handled separately. ⚠️ Partial - Full GDPR suite may require premium
requestDataAccess() Future<UserDataPayload> Implements the Right to Access. Returns information about the user's data. Note: Full implementation typically requires backend integration. ⚠️ Partial - Full GDPR suite may require premium

πŸ’Ž Premium Features #

Some Usercentrics features require a premium account. Check with Usercentrics Sales for details.

Features Requiring Premium #

Feature Method Why Premium?
Cross-Device Consent Sync loginUser(String uid) Requires backend infrastructure to store and synchronize consent across multiple devices.
Multi-Language Support changeLanguage(UsercentricsLanguage language) Ability to add and configure multiple languages in the Usercentrics Dashboard requires a premium account. Free accounts are limited to one language.
Advanced GDPR Tools requestDataDeletion(), requestDataAccess() Full GDPR compliance suite with automated data export and deletion workflows.
Custom Branding Configuration in Dashboard White-label CMP with custom colors, fonts, and logos.
A/B Testing Configuration in Dashboard Test different consent banner designs to optimize consent rates.
Advanced Analytics Dashboard only Detailed reports on consent rates, service adoption, and user behavior.

πŸ’‘ Tip: The core consent management functionality (show banner, accept/deny services) works with free accounts. Multi-language support and cross-device sync require premium plans.


🎯 Advanced Examples #

Conditional Feature Initialization #

class AnalyticsService {
  static final AnalyticsService _instance = AnalyticsService._internal();
  factory AnalyticsService() => _instance;
  AnalyticsService._internal();

  StreamSubscription? _consentSubscription;
  bool _isInitialized = false;

  void initialize() {
    _consentSubscription = UsercentricsManager.instance.consentStream.listen(
      (consents) {
        final analyticsConsent = consents.firstWhere(
          (c) => c.templateId == 'YOUR_ANALYTICS_ID',
          orElse: () => const PrivacyServiceConsent(
            templateId: '',
            status: false,
            name: '',
          ),
        );

        if (analyticsConsent.status && !_isInitialized) {
          _initializeAnalytics();
        } else if (!analyticsConsent.status && _isInitialized) {
          _disableAnalytics();
        }
      },
    );
  }

  void _initializeAnalytics() {
    // Initialize Firebase Analytics, Mixpanel, etc.
    print('πŸ“Š Analytics initialized');
    _isInitialized = true;
  }

  void _disableAnalytics() {
    // Disable tracking
    print('πŸ“Š Analytics disabled');
    _isInitialized = false;
  }

  void dispose() {
    _consentSubscription?.cancel();
  }
}

Custom Privacy Settings Screen #

class PrivacySettingsScreen extends StatelessWidget {
  const PrivacySettingsScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Privacy Settings')),
      body: StreamBuilder<List<PrivacyServiceConsent>>(
        stream: UsercentricsManager.instance.consentStream,
        builder: (context, snapshot) {
          if (!snapshot.hasData) {
            return const Center(child: CircularProgressIndicator());
          }

          final consents = snapshot.data!;

          return ListView(
            children: [
              // Header Section
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Manage Your Privacy',
                      style: Theme.of(context).textTheme.headlineSmall,
                    ),
                    const SizedBox(height: 8),
                    Text(
                      'Control how your data is used',
                      style: Theme.of(context).textTheme.bodyMedium,
                    ),
                  ],
                ),
              ),

              // Quick Actions
              ListTile(
                leading: const Icon(Icons.done_all),
                title: const Text('Accept All'),
                onTap: () async {
                  await UsercentricsManager.instance.setTrackingEnabled(true);
                  if (context.mounted) {
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(content: Text('All services enabled')),
                    );
                  }
                },
              ),
              ListTile(
                leading: const Icon(Icons.block),
                title: const Text('Reject All'),
                onTap: () async {
                  await UsercentricsManager.instance.setTrackingEnabled(false);
                  if (context.mounted) {
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(content: Text('All services disabled')),
                    );
                  }
                },
              ),

              const Divider(),

              // Individual Service Controls
              ...consents.map((consent) => SwitchListTile(
                title: Text(consent.name),
                subtitle: Text('ID: ${consent.templateId}'),
                value: consent.status,
                onChanged: (value) async {
                  await UsercentricsManager.instance.setConsentStatus(
                    consent.templateId,
                    value,
                  );
                },
              )),

              const Divider(),

              // Additional Actions
              ListTile(
                leading: const Icon(Icons.settings),
                title: const Text('Show Full Privacy Manager'),
                onTap: () async {
                  await UsercentricsManager.instance.showPrivacyManager();
                },
              ),
              ListTile(
                leading: const Icon(Icons.delete_forever),
                title: const Text('Delete My Data'),
                onTap: () async {
                  final result = await UsercentricsManager.instance.requestDataDeletion();
                  if (context.mounted) {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(content: Text(result.message)),
                    );
                  }
                },
              ),
            ],
          );
        },
      ),
    );
  }
}

Error Handling #

Future<void> safeInitialize() async {
  try {
    if (UsercentricsManager.instance.isInitialized) {
      print('ℹ️ Already initialized');
      return;
    }

    await UsercentricsManager.instance.initialize(
      settingsId: 'YOUR_SETTINGS_ID',
    );
    
    print('βœ… Initialization successful');
  } on UserscentericsNotInitializedException catch (e) {
    print('❌ Initialization error: $e');
  } catch (e) {
    print('❌ Unexpected error: $e');
  }
}

Future<void> safeConsentOperation() async {
  try {
    await UsercentricsManager.instance.setConsentStatus(
      'ANALYTICS_ID',
      true,
    );
  } on UserscentericsNotInitializedException catch (e) {
    print('❌ Not initialized: ${e.methodName}');
    print('πŸ’‘ Call initialize() first');
  } catch (e) {
    print('❌ Operation failed: $e');
  }
}

πŸ› Troubleshooting #

Common Issues #

1. "Method called before initialize()"

Error: UserscentericsNotInitializedException

Solution: Ensure you call initialize() in main() before runApp():

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await UsercentricsManager.instance.initialize(settingsId: 'YOUR_ID');
  runApp(const MyApp());
}

2. Web: Banner not showing

Possible Causes:

  • CSP blocking the script
  • Settings ID incorrect
  • Network connectivity issues

Solution:

  1. Check browser console for CSP errors
  2. Verify Settings ID in Usercentrics Dashboard
  3. Ensure https://web.cmp.usercentrics.eu is whitelisted in CSP

3. Mobile: Gradle build fails

Error: "Execution failed for task ':app:checkDebugAarMetadata'"

Solution: Update minSdkVersion to 21 in android/app/build.gradle

4. iOS: Pod install fails

Solution:

cd ios
rm -rf Pods Podfile.lock
pod repo update
pod install

Cause: User logged out or cleared app data

Solution: Use loginUser(uid) to restore cross-device consent when user logs in


🌍 Supported Languages #

The package supports 50+ languages via the UsercentricsLanguage enum:

European Languages: English, German, French, Spanish, Italian, Portuguese, Dutch, Polish, Romanian, Swedish, Danish, Finnish, Norwegian, Czech, Hungarian, Greek, Croatian, Bulgarian, Slovak, Slovenian, Serbian, Ukrainian, Russian, Turkish

Asian Languages: Arabic, Hebrew, Hindi, Japanese, Korean, Chinese (Simplified, Traditional, Cantonese), Thai, Vietnamese, Indonesian, Malay, Urdu, Persian (Farsi), Armenian, Georgian, Kazakh, Azerbaijani, Mongolian, Uzbek

African Languages: Afrikaans

Other: Welsh, Catalan, Galician, Icelandic, Albanian, Bosnian, Macedonian, Belarusian, Estonian, Latvian, Lithuanian


πŸ”’ Privacy & Security #

  • No Data Collection: This package does not collect or transmit any data beyond what Usercentrics SDK requires for consent management
  • Local Storage: Consent decisions are stored locally on the device
  • Encryption: Usercentrics SDK uses industry-standard encryption for data transmission
  • GDPR Compliant: Fully compliant with GDPR, CCPA, and other privacy regulations

🀝 About the Author #

Ibrahim El Mourchidi

Ibrahim El Mourchidi

Flutter & Firebase Developer β€’ Cairo, Egypt

GitHub Follow Email LinkedIn Profile

  • πŸ”Ή Top-rated Flutter freelancer (100% Job Success on Upwork)
  • πŸ”Ή Built 20+ production apps with real-time & payment features
  • πŸ”Ή Passionate about clean architecture, compliance, and UX

🀝 Contributing #

Contributions are welcome! Please follow these steps:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

πŸ“ Changelog #

See CHANGELOG.md for version history and updates.


πŸ“„ License #

This project is licensed under the MIT License - see the LICENSE file for details.


Made with ❀️ by Ibrahim El Mourchidi
If this package helped you, consider giving it a ⭐ on GitHub!
0
likes
150
points
78
downloads

Publisher

verified publisherutanium.org

Weekly Downloads

A unified, cross-platform privacy compliance facade for Flutter apps targeting iOS, Android, and Web, built on top of Usercentrics CMP.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, universal_html, usercentrics_sdk

More

Packages that depend on usercentrics_manager