consent_mode_flutter 1.0.2
consent_mode_flutter: ^1.0.2 copied to clipboard
A comprehensive Flutter library for managing user consent according to GDPR, CCPA, and other privacy regulations.
Consent Mode Flutter #
A comprehensive Flutter library for managing user consent according to GDPR, CCPA, and other privacy regulations.
Features #
- πͺ Complete consent management (Essential, Analytics, Marketing, Functional)
- π¨ Customizable UI with banner and bottom sheet
- π¨ Fully custom UI with complete control
- π Listeners to react to consent changes
- πΎ Automatic preference persistence
- π― Simple integration with analytics and marketing tools
Installation #
Add the dependency to your pubspec.yaml
:
dependencies:
consent_mode_flutter: ^1.0.2
Then run:
flutter pub get
Initial Setup #
1. App Initialization #
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize consent mode
await ConsentModeFlutter.initialize();
runApp(const MyApp());
}
2. Basic Configuration #
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Consent Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const ConsentWrapper(),
);
}
}
Available Implementations #
Option 1: Automatic Wrapper (Recommended) #
The simplest way to integrate the consent banner:
class ConsentWrapper extends StatelessWidget {
const ConsentWrapper({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ConsentModeFlutterWrapper(
bannerConfig: ConsentModeFlutterConfigHelper.bannerConfig(context),
bannerStyle: ConsentModeFlutterUiStyleHelper.bannerStyle(context),
child: const MyHomePage(),
);
}
}
Option 2: Custom Bottom Sheet #
For greater control over the user experience:
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final ConsentModeFlutterManager _manager = ConsentModeFlutterManager();
bool _showBanner = false;
@override
void initState() {
super.initState();
_checkBannerVisibility();
}
void _checkBannerVisibility() {
setState(() {
_showBanner = _manager.shouldShowBanner;
});
}
void _showConsentBottomSheet() {
ConsentModeFlutterUtils.showCustomBottomSheet(
context,
ConsentModeFlutterBanner(
config: ConsentModeFlutterConfigHelper.bannerConfig(context),
style: ConsentModeFlutterUiStyleHelper.bannerStyle(context),
onConsentChanged: (Map<ConsentModeFlutterType, bool> preferences) {
// Handle consent changes
_handleConsentChanges(preferences);
},
onDismissed: () {
setState(() {
_showBanner = false;
});
},
),
);
}
void _handleConsentChanges(Map<ConsentModeFlutterType, bool> preferences) {
// Initialize services based on consent
if (preferences[ConsentModeFlutterType.analytics] == true) {
// Initialize Google Analytics, Firebase Analytics, etc.
}
if (preferences[ConsentModeFlutterType.marketing] == true) {
// Initialize Facebook Pixel, Google Ads, etc.
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Consent Mode Demo'),
actions: [
IconButton(
icon: const Icon(Icons.settings),
onPressed: _showConsentBottomSheet,
),
],
),
body: Column(
children: [
if (_showBanner)
Container(
padding: const EdgeInsets.all(16),
color: Theme.of(context).primaryColor.withOpacity(0.1),
child: Row(
children: [
const Expanded(
child: Text('This site uses cookies to improve your experience.'),
),
TextButton(
onPressed: _showConsentBottomSheet,
child: const Text('Manage'),
),
],
),
),
const Expanded(
child: Center(
child: Text('Main app content'),
),
),
],
),
);
}
}
Option 3: Complete Listener #
For total control over consent changes:
class ConsentListenerPage extends StatefulWidget {
const ConsentListenerPage({Key? key}) : super(key: key);
@override
State<ConsentListenerPage> createState() => _ConsentListenerPageState();
}
class _ConsentListenerPageState extends State<ConsentListenerPage> {
Map<ConsentModeFlutterType, bool> _currentPreferences = {};
@override
void initState() {
super.initState();
_loadPreferences();
// Listen to consent changes
ConsentModeFlutter.addListener(_onConsentChanged);
}
@override
void dispose() {
ConsentModeFlutter.removeListener(_onConsentChanged);
super.dispose();
}
void _loadPreferences() {
setState(() {
_currentPreferences = ConsentModeFlutter.preferences;
});
}
void _onConsentChanged(Map<ConsentModeFlutterType, bool> preferences) {
setState(() {
_currentPreferences = preferences;
});
// Initialize services based on consent
_initializeServices(preferences);
}
void _initializeServices(Map<ConsentModeFlutterType, bool> preferences) {
if (ConsentModeFlutter.isConsentGranted(ConsentModeFlutterType.essential)) {
debugPrint('Essential consent granted - initialize essential services');
// Initialize essential services
}
if (ConsentModeFlutter.isConsentGranted(ConsentModeFlutterType.analytics)) {
debugPrint('Analytics consent granted - initialize analytics');
// Initialize Google Analytics, Firebase Analytics, etc.
}
if (ConsentModeFlutter.isConsentGranted(ConsentModeFlutterType.marketing)) {
debugPrint('Marketing consent granted - initialize marketing tools');
// Initialize Facebook Pixel, Google Ads, etc.
}
if (ConsentModeFlutter.isConsentGranted(ConsentModeFlutterType.functional)) {
debugPrint('Functional consent granted - initialize functional services');
// Initialize functional services
}
}
Future<void> _resetConsent() async {
await ConsentModeFlutter.reset();
setState(() {
_currentPreferences = ConsentModeFlutter.preferences;
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Consent preferences reset. Restart app to see banner again.',
),
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Consent Listener'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _resetConsent,
),
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Current Consent Status:',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
...ConsentModeFlutterType.values.map((type) {
final granted = _currentPreferences[type] ?? false;
return ListTile(
leading: Icon(
granted ? Icons.check_circle : Icons.cancel,
color: granted ? Colors.green : Colors.red,
),
title: Text(type.name),
subtitle: Text(granted ? 'Granted' : 'Denied'),
);
}).toList(),
],
),
),
);
}
}
Option 4: Fully Custom UI (NEW) #
π¨ For complete control over the consent UI design
When you need to create a completely custom consent interface that doesn't follow the standard design patterns, you can use the custom UI approach:
class ConsentCustomUIWrapper extends StatelessWidget {
const ConsentCustomUIWrapper({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ConsentModeFlutterWrapper(
bannerCustomConfigUIStyle: ConsentModeFlutterCustomConfigUiStyleHelper
.bannerCustomConfigUIStyle(context),
child: const MyHomePage(),
);
}
}
Custom UI Helper Class
Create a helper class for your completely custom consent UI:
class ConsentModeFlutterCustomConfigUiStyleHelper {
static ConsentModeFlutterBannerCustomConfigUIStyle bannerCustomConfigUIStyle(
BuildContext context,
) {
double width = MediaQuery.of(context).size.width;
final ConsentModeFlutterManager manager = ConsentModeFlutterManager();
return ConsentModeFlutterBannerCustomConfigUIStyle(
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.1),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
width: width,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Your completely custom UI here
const Icon(Icons.cookie, size: 48, color: Colors.brown),
const SizedBox(height: 16),
const Text(
'Cookie Preferences',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
const Text(
'We use cookies to enhance your experience. Please choose your preferences.',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16),
),
const SizedBox(height: 20),
// Custom buttons with your own logic
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () {
// Handle custom consent logic
_handleAcceptAll(context, manager);
},
child: const Text('Accept All'),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: () {
// Handle custom consent logic
_handleRejectAll(context, manager);
},
child: const Text('Reject All'),
),
),
],
),
],
),
),
);
}
static Future<void> _handleAcceptAll(
BuildContext context,
ConsentModeFlutterManager manager,
) async {
try {
await manager.acceptAll();
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Error saving preferences: $e')));
}
}
}
static Future<void> _handleRejectAll(
BuildContext context,
ConsentModeFlutterManager manager,
) async {
try {
await manager.rejectAll();
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Error saving preferences: $e')));
}
}
}
}
Using Custom UI in Bottom Sheet
You can also use the custom UI approach with the bottom sheet method:
void _showCustomConsentBottomSheet() {
ConsentModeFlutterUtils.showCustomBottomSheet(
context,
ConsentModeFlutterCustomConfigUiStyleHelper
.bannerCustomConfigUIStyle(context)
.child,
);
}
Advantages of Custom UI Approach
- β Complete Design Control: Create any UI design you want
- β Brand Consistency: Match exactly your app's design system
- β Custom Interactions: Implement custom animations and interactions
- β Flexible Layout: Not limited by predefined banner structures
- β Advanced UX: Create multi-step flows, tabs, or any complex UI pattern
When to Use Custom UI
- Brand Requirements: When your brand guidelines require a specific design
- Complex Flows: When you need multi-step consent processes
- Advanced UX: When you want to implement custom animations or interactions
- Unique Layouts: When standard banner layouts don't fit your needs
- Integration Requirements: When you need to integrate consent UI with other app features
Advanced Configuration #
Banner Customization #
Recommended Approach: Helper Classes
It's strongly recommended to create helper classes to centralize configuration. This approach offers numerous advantages:
- β Reusability: Once created, you can call them anywhere in the app
- β Maintenance: Changes in one place only
- β Consistency: Uniform style throughout the app
- β Ease of use: Cleaner and more readable code
// Create these helper classes in your project
class ConsentModeFlutterConfigHelper {
static ConsentModeFlutterBannerStandardConfig bannerConfig(
BuildContext context,
) {
return ConsentModeFlutterBannerStandardConfig(
privacyPolicyUrl: 'https://example.com/privacy',
title: 'Privacy & Cookies',
description: 'We use cookies and similar technologies to improve your experience...',
acceptAllText: 'Accept All',
rejectAllText: 'Reject All',
essentialTitle: 'Essential Cookies',
analyticsTitle: 'Analytics Cookies',
marketingTitle: 'Marketing Cookies',
functionalTitle: 'Functional Cookies',
showRejectAll: true,
showMoreInformation: true,
);
}
}
class ConsentModeFlutterUiStyleHelper {
static ConsentModeFlutterStandardUIStyle bannerStyle(BuildContext context) {
return ConsentModeFlutterStandardUIStyle(
image: Image.asset(
'assets/images/brand_flutter.webp',
width: 70,
height: 70,
),
title: const TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
color: Colors.black,
),
subTitle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black,
),
description: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
),
urlPolicy: const TextStyle(color: CustomColors.kBlueBottone),
buttonStyle: ButtonStyle(
backgroundColor: WidgetStateProperty.all(CustomColors.kBlueBottone),
foregroundColor: WidgetStateProperty.all(Colors.black),
padding: WidgetStateProperty.all(
const EdgeInsets.symmetric(
horizontal: 24,
vertical: 16,
),
),
shape: WidgetStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
textStyle: WidgetStateProperty.all(
const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
),
);
}
}
Now you can use them anywhere in the app simply like this:
// Anywhere in your app
ConsentModeFlutterBanner(
config: ConsentModeFlutterConfigHelper.bannerConfig(context),
style: ConsentModeFlutterUiStyleHelper.bannerStyle(context),
onConsentChanged: (preferences) {
// Handle changes
},
onDismissed: () {
// Handle dismissal
},
)
// Or in the wrapper
ConsentModeFlutterWrapper(
bannerConfig: ConsentModeFlutterConfigHelper.bannerConfig(context),
bannerStyle: ConsentModeFlutterUiStyleHelper.bannerStyle(context),
child: MyHomePage(),
)
// Or in the bottom sheet
ConsentModeFlutterUtils.showCustomBottomSheet(
context,
ConsentModeFlutterBanner(
config: ConsentModeFlutterConfigHelper.bannerConfig(context),
style: ConsentModeFlutterUiStyleHelper.bannerStyle(context),
onConsentChanged: (preferences) {},
onDismissed: () {},
),
);
Alternative Approach: Direct Configuration
If you prefer a more direct approach (less recommended for large projects):
// Direct custom configuration
ConsentModeFlutterConfig customConfig = ConsentModeFlutterConfig(
title: 'Privacy & Cookies',
description: 'We use cookies and similar technologies to improve your experience...',
acceptAllText: 'Accept All',
rejectAllText: 'Reject All',
settingsText: 'Manage Preferences',
essentialTitle: 'Essential Cookies',
analyticsTitle: 'Analytics Cookies',
marketingTitle: 'Marketing Cookies',
functionalTitle: 'Functional Cookies',
);
// Direct custom style
ConsentModeFlutterUiStyle customStyle = ConsentModeFlutterUiStyle(
backgroundColor: Colors.white,
textColor: Colors.black87,
buttonColor: Colors.blue,
buttonTextColor: Colors.white,
borderRadius: 12.0,
);
Advantages of the Helper Approach
- Centralization: All configurations in one place
- Dynamic Theme: Can automatically adapt to the app's theme
- Reusability: Once written, usable everywhere
- Maintainability: Easy to update and modify
- Consistency: Ensures the same look throughout the app
Analytics Integration #
void _initializeAnalytics() {
if (ConsentModeFlutter.isConsentGranted(ConsentModeFlutterType.analytics)) {
// Initialize Firebase Analytics
FirebaseAnalytics.instance.setAnalyticsCollectionEnabled(true);
// Initialize Google Analytics
// GoogleAnalytics.initialize();
} else {
// Disable analytics
FirebaseAnalytics.instance.setAnalyticsCollectionEnabled(false);
}
}
Consent Types #
The library supports four types of consent:
- Essential: Cookies and services essential for site functionality
- Analytics: Analysis tools and performance measurement
- Marketing: Cookies for personalized advertising and remarketing
- Functional: Services that improve user experience (chat, maps, etc.)
Main APIs #
Static Methods #
// Check if consent has been granted
bool isGranted = ConsentModeFlutter.isConsentGranted(ConsentModeFlutterType.analytics);
// Get all preferences
Map<ConsentModeFlutterType, bool> preferences = ConsentModeFlutter.preferences;
// Reset all consents
await ConsentModeFlutter.reset();
// Add/Remove listeners
ConsentModeFlutter.addListener(callback);
ConsentModeFlutter.removeListener(callback);
Manager #
final manager = ConsentModeFlutterManager();
// Check if banner should be shown
bool shouldShow = manager.shouldShowBanner;
// Force banner display
manager.showBanner();
Best Practices #
1. Early Initialization #
Always initialize consent mode before any other service that requires consent.
2. Service Management #
void _manageServices() {
// Analytics
if (ConsentModeFlutter.isConsentGranted(ConsentModeFlutterType.analytics)) {
_enableAnalytics();
} else {
_disableAnalytics();
}
// Marketing
if (ConsentModeFlutter.isConsentGranted(ConsentModeFlutterType.marketing)) {
_enableMarketing();
} else {
_disableMarketing();
}
}
3. Privacy Respect #
- Always ask for consent before tracking
- Provide granular options
- Allow users to change their mind
- Document what your cookies do
4. UI Implementation Choice #
Choose the right implementation based on your needs:
- Use Standard Banner (Option 1-2) for quick implementation with good customization
- Use Custom UI (Option 4) when you need complete design control
- Use Listeners (Option 3) when you need to react to consent changes in real-time
Troubleshooting #
Banner not shown #
- Verify that
ConsentModeFlutter.initialize()
is called - Check that the banner hasn't already been accepted/rejected
- Use
ConsentModeFlutter.reset()
for testing
Consents not persistent #
- Verify app write permissions
- Check that initialization is completed before use
Listeners not working #
- Make sure to call
addListener
afterinitState
- Don't forget
removeListener
indispose
Custom UI not working #
- Ensure
bannerCustomConfigUIStyle
is properly configured - Verify that your custom widget handles consent logic properly
- Check that navigation/dismissal is implemented correctly
Complete Example #
See the example/lib/main.dart
file in the repository for a complete working example.
License #
MIT License - see the LICENSE file for details.
Contributions #
Contributions are welcome! Open an issue or pull request for improvements.
Note: This library helps you manage consents, but it's your responsibility to ensure the implementation complies with local privacy laws (GDPR, CCPA, etc.).