usercentrics_manager 0.1.0
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.
π Table of Contents #
- Features
- Installation
- Platform Setup
- Quick Start
- Detailed Usage
- Complete API Reference
- Premium Features
- Advanced Examples
- Troubleshooting
- About the Author
- License
β¨ Features #
- β
Unified API: Single
UsercentricsManager.instanceinterface 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_sdkon 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_sdkoruniversal_htmlβ they're included as transitive dependencies.
Then run:
flutter pub get
π§ Platform Setup #
π€ Android #
Minimum Requirements:
minSdkVersion 21or highercompileSdkVersion 33or 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.plistchanges 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());
}
2. Show Consent Banner #
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 #
Listening to Consent Changes #
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().
Programmatic Consent Management #
// 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 |
Consent Operations #
| 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:
- Check browser console for CSP errors
- Verify Settings ID in Usercentrics Dashboard
- Ensure
https://web.cmp.usercentrics.euis 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
5. Consent not persisting
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 #
- πΉ 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:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - 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.
If this package helped you, consider giving it a β on GitHub!