# User Consent Management Implement GDPR/CCPA compliant opt-in/opt-out flows in Flutter # User Consent Management Implement comprehensive user consent management for GDPR, CCPA, and other privacy regulations while maintaining the full power of UXCam's analytics capabilities. ## Overview User consent management is essential for: * **GDPR Compliance** (European Union) * **CCPA Compliance** (California) * **Privacy-First Development** (Global best practice) * **User Trust Building** (Transparent data practices) UXCam's Flutter SDK provides flexible consent management that allows users to control their data while giving you the insights you need. ## Basic Consent Implementation ### Simple Opt-In/Opt-Out ```dart import 'package:flutter_uxcam/flutter_uxcam.dart'; class ConsentManager { static const String CONSENT_KEY = 'uxcam_user_consent'; // Check current consent status static Future hasUserConsented() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); return prefs.getBool(CONSENT_KEY) ?? false; } // Grant consent and start recording static Future grantConsent() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.setBool(CONSENT_KEY, true); // Opt into video recordings FlutterUxcam.optIntoVideoRecordings(); // Start UXCam with configuration FlutterUxConfig config = FlutterUxConfig( userAppKey: "YOUR_APP_KEY", enableAutomaticScreenNameTagging: true, ); await FlutterUxcam.startWithConfiguration(config); print('User has granted consent - UXCam recording started'); } // Revoke consent and stop recording static Future revokeConsent() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.setBool(CONSENT_KEY, false); // Opt out of recordings FlutterUxcam.optOutOfVideoRecordings(); // Stop current session await FlutterUxcam.stopSessionAndUploadData(); print('User has revoked consent - UXCam recording stopped'); } // Initialize UXCam based on consent status static Future initializeWithConsent(String appKey) async { final hasConsent = await hasUserConsented(); if (hasConsent) { await grantConsent(); } else { // Show consent dialog or handle no-consent state await _handleNoConsent(); } } static Future _handleNoConsent() async { // Could show consent dialog or just log print('User has not granted consent - UXCam not initialized'); } } ``` ### Consent Dialog Implementation ```dart class ConsentDialog extends StatefulWidget { final VoidCallback onConsentGiven; final VoidCallback onConsentDenied; const ConsentDialog({ Key? key, required this.onConsentGiven, required this.onConsentDenied, }) : super(key: key); @override _ConsentDialogState createState() => _ConsentDialogState(); } class _ConsentDialogState extends State { bool _analyticsConsent = false; bool _crashReportingConsent = false; @override Widget build(BuildContext context) { return AlertDialog( title: Text('Privacy & Data Collection'), content: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( 'We use analytics to improve your experience. You can control what data we collect:', style: TextStyle(fontSize: 16), ), SizedBox(height: 16), CheckboxListTile( title: Text('User Analytics'), subtitle: Text('Help us understand how you use the app'), value: _analyticsConsent, onChanged: (value) { setState(() { _analyticsConsent = value ?? false; }); }, ), CheckboxListTile( title: Text('Crash Reporting'), subtitle: Text('Help us fix bugs and improve stability'), value: _crashReportingConsent, onChanged: (value) { setState(() { _crashReportingConsent = value ?? false; }); }, ), SizedBox(height: 16), Text( 'You can change these preferences anytime in Settings.', style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), ], ), ), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); widget.onConsentDenied(); }, child: Text('Decline All'), ), ElevatedButton( onPressed: (_analyticsConsent || _crashReportingConsent) ? () { Navigator.of(context).pop(); _handleConsentGiven(); } : null, child: Text('Accept Selected'), ), ], ); } void _handleConsentGiven() { // Store granular consent preferences _storeConsentPreferences(_analyticsConsent, _crashReportingConsent); widget.onConsentGiven(); } Future _storeConsentPreferences( bool analytics, bool crashReporting, ) async { final SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.setBool('analytics_consent', analytics); await prefs.setBool('crash_reporting_consent', crashReporting); } } // Usage in your app class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State { bool _consentChecked = false; @override void initState() { super.initState(); _checkAndRequestConsent(); } Future _checkAndRequestConsent() async { final hasConsent = await ConsentManager.hasUserConsented(); if (!hasConsent) { // Show consent dialog WidgetsBinding.instance.addPostFrameCallback((_) { _showConsentDialog(); }); } else { // Initialize with existing consent await ConsentManager.initializeWithConsent("YOUR_APP_KEY"); setState(() { _consentChecked = true; }); } } void _showConsentDialog() { showDialog( context: context, barrierDismissible: false, builder: (context) => ConsentDialog( onConsentGiven: () async { await ConsentManager.grantConsent(); setState(() { _consentChecked = true; }); }, onConsentDenied: () { setState(() { _consentChecked = true; }); }, ), ); } } ``` ## Advanced Consent Management ### Granular Consent Controls ```dart class GranularConsentManager { static const String ANALYTICS_CONSENT_KEY = 'uxcam_analytics_consent'; static const String CRASH_CONSENT_KEY = 'uxcam_crash_consent'; static const String PERFORMANCE_CONSENT_KEY = 'uxcam_performance_consent'; static const String CONSENT_VERSION_KEY = 'uxcam_consent_version'; static const int CURRENT_CONSENT_VERSION = 2; // Consent types enum enum ConsentType { analytics, crashReporting, performanceMonitoring, } // Check if consent needs to be refreshed static Future needsConsentRefresh() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); final currentVersion = prefs.getInt(CONSENT_VERSION_KEY) ?? 0; return currentVersion < CURRENT_CONSENT_VERSION; } // Get consent for specific type static Future hasConsentFor(ConsentType type) async { final SharedPreferences prefs = await SharedPreferences.getInstance(); final key = _getConsentKey(type); return prefs.getBool(key) ?? false; } // Set consent for specific type static Future setConsentFor(ConsentType type, bool granted) async { final SharedPreferences prefs = await SharedPreferences.getInstance(); final key = _getConsentKey(type); await prefs.setBool(key, granted); // Update consent version await prefs.setInt(CONSENT_VERSION_KEY, CURRENT_CONSENT_VERSION); // Update UXCam configuration based on consent await _updateUXCamConfiguration(); } // Set multiple consents at once static Future setConsents(Map consents) async { final SharedPreferences prefs = await SharedPreferences.getInstance(); for (final entry in consents.entries) { final key = _getConsentKey(entry.key); await prefs.setBool(key, entry.value); } await prefs.setInt(CONSENT_VERSION_KEY, CURRENT_CONSENT_VERSION); await _updateUXCamConfiguration(); } // Get all consents static Future> getAllConsents() async { final result = {}; for (final type in ConsentType.values) { result[type] = await hasConsentFor(type); } return result; } // Update UXCam configuration based on current consents static Future _updateUXCamConfiguration() async { final analyticsConsent = await hasConsentFor(ConsentType.analytics); final crashConsent = await hasConsentFor(ConsentType.crashReporting); final performanceConsent = await hasConsentFor(ConsentType.performanceMonitoring); if (analyticsConsent || crashConsent || performanceConsent) { // User has granted some form of consent FlutterUxcam.optIntoVideoRecordings(); FlutterUxConfig config = FlutterUxConfig( userAppKey: "YOUR_APP_KEY", enableAutomaticScreenNameTagging: analyticsConsent, enableCrashHandling: crashConsent, enableImprovedScreenCapture: performanceConsent, ); await FlutterUxcam.startWithConfiguration(config); } else { // User has denied all consent FlutterUxcam.optOutOfVideoRecordings(); await FlutterUxcam.stopSessionAndUploadData(); } } static String _getConsentKey(ConsentType type) { switch (type) { case ConsentType.analytics: return ANALYTICS_CONSENT_KEY; case ConsentType.crashReporting: return CRASH_CONSENT_KEY; case ConsentType.performanceMonitoring: return PERFORMANCE_CONSENT_KEY; } } // Clear all consent data (for account deletion) static Future clearAllConsents() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); for (final type in ConsentType.values) { await prefs.remove(_getConsentKey(type)); } await prefs.remove(CONSENT_VERSION_KEY); // Stop UXCam and clear data FlutterUxcam.optOutOfVideoRecordings(); await FlutterUxcam.stopSessionAndUploadData(); } } ``` ### Regional Compliance ```dart class RegionalComplianceManager { // Detect user's region for compliance requirements static Future detectUserRegion() async { try { // Use a geolocation service or device locale final locale = Localizations.localeOf(context); final countryCode = locale.countryCode ?? 'US'; return countryCode; } catch (e) { // Default to most restrictive (EU) if detection fails return 'EU'; } } // Check if region requires explicit consent static bool requiresExplicitConsent(String region) { const gdprRegions = ['AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'GB', 'EU']; const ccpaRegions = ['CA']; // California return gdprRegions.contains(region) || ccpaRegions.contains(region); } // Get compliance requirements for region static ComplianceRequirements getComplianceRequirements(String region) { if (region == 'CA') { return ComplianceRequirements.ccpa(); } else if (['GB', 'EU'].contains(region) || requiresExplicitConsent(region)) { return ComplianceRequirements.gdpr(); } else { return ComplianceRequirements.minimal(); } } // Initialize UXCam with region-appropriate consent flow static Future initializeWithRegionalCompliance() async { final region = await detectUserRegion(); final requirements = getComplianceRequirements(region); if (requirements.requiresExplicitConsent) { // Check if we have valid consent final needsConsent = await GranularConsentManager.needsConsentRefresh(); final hasAnyConsent = await _hasAnyConsent(); if (needsConsent || !hasAnyConsent) { // Show appropriate consent dialog await _showRegionalConsentDialog(requirements); } else { // Use existing consent await GranularConsentManager._updateUXCamConfiguration(); } } else { // Region doesn't require explicit consent, use default settings await _initializeWithDefaultSettings(); } } static Future _hasAnyConsent() async { final consents = await GranularConsentManager.getAllConsents(); return consents.values.any((consent) => consent); } static Future _showRegionalConsentDialog( ComplianceRequirements requirements, ) async { // Implementation depends on your UI framework // This would show a dialog appropriate for the compliance requirements } static Future _initializeWithDefaultSettings() async { FlutterUxConfig config = FlutterUxConfig( userAppKey: "YOUR_APP_KEY", enableAutomaticScreenNameTagging: true, enableCrashHandling: true, ); await FlutterUxcam.startWithConfiguration(config); } } class ComplianceRequirements { final bool requiresExplicitConsent; final bool allowsOptOut; final bool requiresDataDeletion; final List requiredDisclosures; ComplianceRequirements({ required this.requiresExplicitConsent, required this.allowsOptOut, required this.requiresDataDeletion, required this.requiredDisclosures, }); factory ComplianceRequirements.gdpr() { return ComplianceRequirements( requiresExplicitConsent: true, allowsOptOut: true, requiresDataDeletion: true, requiredDisclosures: [ 'What data we collect', 'How we use your data', 'Your rights under GDPR', 'How to contact us', ], ); } factory ComplianceRequirements.ccpa() { return ComplianceRequirements( requiresExplicitConsent: false, allowsOptOut: true, requiresDataDeletion: true, requiredDisclosures: [ 'Categories of data collected', 'Business purposes for collection', 'Right to opt-out', 'Right to delete', ], ); } factory ComplianceRequirements.minimal() { return ComplianceRequirements( requiresExplicitConsent: false, allowsOptOut: true, requiresDataDeletion: false, requiredDisclosures: [ 'Data collection notice', ], ); } } ``` ## Consent Persistence and Migration ### Consent Data Migration ```dart class ConsentMigrationManager { static Future migrateConsentData() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); final currentVersion = prefs.getInt('consent_data_version') ?? 0; if (currentVersion < 1) { await _migrateToV1(); } if (currentVersion < 2) { await _migrateToV2(); } // Update version await prefs.setInt('consent_data_version', 2); } static Future _migrateToV1() async { // Migration from legacy boolean consent to granular consent final SharedPreferences prefs = await SharedPreferences.getInstance(); final legacyConsent = prefs.getBool('uxcam_user_consent') ?? false; if (legacyConsent) { // Migrate to new granular system with all permissions await GranularConsentManager.setConsents({ GranularConsentManager.ConsentType.analytics: true, GranularConsentManager.ConsentType.crashReporting: true, GranularConsentManager.ConsentType.performanceMonitoring: true, }); } // Remove legacy key await prefs.remove('uxcam_user_consent'); } static Future _migrateToV2() async { // Add new consent types or update existing ones // This is where you'd handle future consent migrations } } ``` ## Settings Integration ### Privacy Settings Screen ```dart class PrivacySettingsScreen extends StatefulWidget { @override _PrivacySettingsScreenState createState() => _PrivacySettingsScreenState(); } class _PrivacySettingsScreenState extends State { Map _consents = {}; bool _loading = true; @override void initState() { super.initState(); _loadConsents(); } Future _loadConsents() async { final consents = await GranularConsentManager.getAllConsents(); setState(() { _consents = consents; _loading = false; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Privacy Settings'), ), body: _loading ? Center(child: CircularProgressIndicator()) : ListView( padding: EdgeInsets.all(16), children: [ Text( 'Data Collection Preferences', style: Theme.of(context).textTheme.headline6, ), SizedBox(height: 16), _buildConsentTile( GranularConsentManager.ConsentType.analytics, 'Analytics & Usage Data', 'Help us understand how you use the app to improve your experience', Icons.analytics, ), _buildConsentTile( GranularConsentManager.ConsentType.crashReporting, 'Crash Reports', 'Automatically report crashes to help us fix bugs', Icons.bug_report, ), _buildConsentTile( GranularConsentManager.ConsentType.performanceMonitoring, 'Performance Monitoring', 'Monitor app performance to optimize your experience', Icons.speed, ), SizedBox(height: 32), _buildActionButtons(), ], ), ); } Widget _buildConsentTile( GranularConsentManager.ConsentType type, String title, String description, IconData icon, ) { return Card( child: SwitchListTile( title: Text(title), subtitle: Text(description), secondary: Icon(icon), value: _consents[type] ?? false, onChanged: (bool value) async { setState(() { _consents[type] = value; }); await GranularConsentManager.setConsentFor(type, value); }, ), ); } Widget _buildActionButtons() { return Column( children: [ SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _exportData, child: Text('Export My Data'), ), ), SizedBox(height: 8), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _deleteData, style: ElevatedButton.styleFrom( backgroundColor: Colors.red, ), child: Text('Delete My Data'), ), ), ], ); } Future _exportData() async { // Implement data export functionality ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Data export requested')), ); } Future _deleteData() async { final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: Text('Delete All Data'), content: Text('This will permanently delete all your data. This cannot be undone.'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: Text('Cancel'), ), ElevatedButton( onPressed: () => Navigator.of(context).pop(true), style: ElevatedButton.styleFrom(backgroundColor: Colors.red), child: Text('Delete'), ), ], ), ); if (confirmed == true) { await GranularConsentManager.clearAllConsents(); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('All data deleted')), ); } } } ``` ## Best Practices ### Do's * ✅ Always check consent before initializing UXCam * ✅ Provide clear, understandable consent language * ✅ Allow users to change their preferences anytime * ✅ Implement granular consent controls * ✅ Handle consent migration properly * ✅ Respect regional compliance requirements ### Don'ts * ❌ Don't collect data without proper consent * ❌ Don't make consent mandatory for app functionality * ❌ Don't use confusing or misleading consent language * ❌ Don't ignore user consent preferences * ❌ Don't forget to handle consent expiration * ❌ Don't skip regional compliance considerations ## Next Steps * **[Integration Logging](integration-logging-guide-flutter.md)** - Debug your consent setup * **[Troubleshooting](troubleshooting-faqs-flutter.md)** - Solve consent-related issues * **[Recording Control](control-recording-flutter.md)** - Control recording based on consent *** *Proper consent management builds user trust while keeping you compliant with privacy regulations.*