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

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

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

  1. Centralization: All configurations in one place
  2. Dynamic Theme: Can automatically adapt to the app's theme
  3. Reusability: Once written, usable everywhere
  4. Maintainability: Easy to update and modify
  5. 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);
  }
}

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

  1. Verify that ConsentModeFlutter.initialize() is called
  2. Check that the banner hasn't already been accepted/rejected
  3. Use ConsentModeFlutter.reset() for testing

Consents not persistent

  1. Verify app write permissions
  2. Check that initialization is completed before use

Listeners not working

  1. Make sure to call addListener after initState
  2. Don't forget removeListener in dispose

Custom UI not working

  1. Ensure bannerCustomConfigUIStyle is properly configured
  2. Verify that your custom widget handles consent logic properly
  3. 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.).