GDPR Compliance in Flutter Mobile Applications
GDPR Scope
If an app serves even a single user in the EU, it falls under the scope of GDPR. This has been explicitly stated by the European Court of Justice and within the GDPR text itself.
So, if you publish your Flutter app on the App Store / Google Play and it is downloaded from the EU, GDPR obligations automatically apply.
The Cost of Non-Compliance
When it comes to GDPR violations, penalties are truly deterrent:
- Fines up to 20 million Euros
- 4% of the company’s annual global revenue (whichever is higher will be applied)
These aren’t just theoretical numbers. In recent years, many companies worldwide have paid millions of euros in fines. Even for a small startup, these amounts can mean the end of the company.
Purpose of This Guide
This document aims to provide a step-by-step practical guide for Flutter developers to ensure GDPR compliance. Instead of theoretical information, you’ll find concrete solutions and best practices that you can integrate into your application today.
GDPR is built upon 7 core principles of data protection. Understanding these principles will help you make the right decisions when designing your application.
1. Lawfulness, Fairness, and Transparency
Personal data must be processed lawfully, fairly, and in a transparent manner.
🎯 Tell users CLEARLY what data you collect and why. Hidden data collection is a GDPR violation
// ✅ Good Example: Transparent information
void showPrivacyNotice() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Privacy and Your Data'),
content: Text(
'Our app collects the following data to provide you with better service:\n\n'
'• Usage analytics (which features you use)\n'
'• Location information (for nearby stores - with your permission)\n'
'• Device information (for app compatibility)\n\n'
'You can read in detail how we use this data in our Privacy Policy.'
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('Read Privacy Policy'),
),
ElevatedButton(
onPressed: () => Navigator.pop(context),
child: Text('I Understand'),
),
],
),
);
}
// ❌ Bad Example: Vague information
void wrongApproach() {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: Text('By using this app, you agree to our data collection'),
// What data? For what purpose? VAGUE - GDPR VIOLATION!
),
);
}
2. Purpose Limitation
Data must be collected for specified, explicit, and legitimate purposes and not processed in a manner incompatible with those purposes.
🎯 Data collected for analytics CANNOT be used for marketing (without new consent)!
// ❌ WRONG: Using data for different purposes
class WrongPurposeUsage {
Future<void> collectAnalytics() async {
// User gave permission for "analytics"
final userBehavior = await getUserBehaviorData();
// But it's being used for marketing - GDPR VIOLATION!
await sendToMarketingTeam(userBehavior); // ❌
await targetAdsBasedOnBehavior(userBehavior); // ❌
}
}
// ✅ CORRECT: Purpose-based permission
class CorrectPurposeUsage {
Future<void> collectData() async {
// SEPARATE consent for each purpose
final analyticsConsent = await getAnalyticsConsent();
final marketingConsent = await getMarketingConsent();
if (analyticsConsent) {
// Only for analytics purposes
await sendToAnalytics(userData);
}
if (marketingConsent) {
// Marketing only with separate consent
await sendToMarketing(userData);
}
}
}
3. Data Minimisation
Collect only the data you need — nothing more.
🎯 “Will the app not work without this data?” If the answer is NO, DON’T COLLECT IT!
// ❌ Bad Example: Excessive data collection
class UserProfile {
String name;
String email;
String phone; // Really necessary?
String address; // Really necessary?
String birthDate; // Really necessary?
String gender; // Really necessary?
String socialSecurityNumber; // NEVER COLLECT!
String motherMaidenName; // NEVER COLLECT!
double latitude; // Always necessary?
double longitude; // Always necessary?
}
// ✅ Good Example - Only REQUIRED data
class UserProfile {
String userId; // REQUIRED for account management
String email; // REQUIRED for communication
// Nothing else!
}
// Extra data only if user wants
class OptionalUserData {
String? displayName; // User adds themselves (optional)
String? avatarUrl; // User adds themselves (optional)
}
4. Accuracy
Personal data must be correct and kept up to date.
🎯 Users must always have the ability to review and correct their personal data without delay!
// Allow users to update their data
class ProfileUpdatePage extends StatelessWidget {
void updateUserData(String newEmail) async {
await userRepository.updateEmail(newEmail);
// Update in database
await database.update('users', {'email': newEmail});
}
}
5. Storage Limitation
Keep data only for as long as necessary.
🎯 If the data is no longer needed for its original purpose, it must be deleted or anonymized immediately!
// ❌ Bad Example: Data kept forever
class DataRetentionService {
Future<void> doNothing() async {
// User data and analytics never deleted
// ❌ Endless storage = GDPR violation!
}
}
// ✅ Good Example: Data automatically removed
class DataRetentionService {
// Auto-delete analytics older than 30 days
Future<void> cleanupOldData() async {
final thirtyDaysAgo = DateTime.now().subtract(Duration(days: 30));
await database.delete(
'analytics',
where: 'created_at < ?',
whereArgs: [thirtyDaysAgo.toIso8601String()],
);
}
// Delete all user data if account removed
Future<void> deleteAllUserData(String userId) async {
await database.delete('users', where: 'id = ?', whereArgs: [userId]);
await database.delete('user_sessions', where: 'user_id = ?', whereArgs: [userId]);
await database.delete('user_preferences', where: 'user_id = ?', whereArgs: [userId]);
}
}
6. Integrity and Confidentiality
Personal data must be processed securely and protected from unauthorized access, loss, or damage.
🎯 Always encrypt and protect personal data. IP address, location (lat/long), PII must NOT be sent to third parties
// ✅ Encrypt sensitive data
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class SecureDataService {
final storage = FlutterSecureStorage();
Future<void> saveSecureData(String key, String value) async {
await storage.write(
key: key,
value: value,
aOptions: AndroidOptions(encryptedSharedPreferences: true),
);
}
Future<String?> getSecureData(String key) async {
return await storage.read(key: key);
}
}
// ❌ BAD: Sending PII to third parties
class WrongApproach {
void trackUser() async {
await analytics.logEvent('user_data', {
'ip_address': ip, // 🔴 GDPR VIOLATION!
'latitude': lat, // 🔴 GDPR VIOLATION!
'email': user.email, // 🔴 GDPR VIOLATION!
});
}
}
// ✅ GOOD: Use anonymous IDs, no PII to third parties
class CorrectApproach {
void trackUser() async {
final anonymousId = Uuid().v4();
await analytics.setUserId(anonymousId); // ✅ Anonymous
// City/country instead of exact location
final city = await getCityFromCoordinates(lat, lon);
await analytics.logEvent('user_region', {
'city': city, // ✅ General info, no lat/long
});
}
}
7. Accountability
You must be able to demonstrate GDPR compliance at any time. Documentation, logs, and audit trails are essential.
🎯 Get OPT-IN consent BEFORE entering the app! Keep audit logs — if you cannot prove compliance, regulators assume non-compliance!
// ✅ Opt-in approach
class CorrectConsentApproach {
void initApp() async {
final hasConsent = await checkGDPRConsent();
if (!hasConsent) {
await showMandatoryConsentScreen();
if (!await checkGDPRConsent()) {
initBasicFeaturesOnly();
return;
}
}
await FirebaseAnalytics.instance.logAppOpen(); // ✅ After consent
}
}
// ❌ Bad Example: No logging or audit trail
class UserService {
Future<void> updateEmail(String newEmail) async {
await database.update('users', {'email': newEmail});
// ❌ No record of who changed it, when, or why
}
}
// ✅ Good Example: Keep audit logs for accountability
class UserService {
Future<void> updateEmail(String userId, String newEmail) async {
await database.update('users', {'email': newEmail});
await database.insert('audit_logs', {
'user_id': userId,
'action': 'update_email',
'new_value': newEmail,
'timestamp': DateTime.now().toIso8601String(),
});
// ✅ Now you can prove changes if regulators ask
}
}
GDPR Critical Requirements
The following are the most common GDPR violations in Flutter apps. Each one can result in significant fines. Make sure your app complies with ALL of these before launching.
1. Third-Party Package Management
Every package you add is a potential GDPR violation risk. Each package may collect data without your knowledge!
🎯 Minimize third-party packages as much as possible!
// ❌ BAD: Too many packages
dependencies:
awesome_analytics: ^1.0.0 # What does it collect? Unknown!
tracking_package: ^2.0.0 # Another tracking SDK
data_collector: ^1.5.0 # Unclear privacy policy
cool_feature: ^3.0.0 # Entire package for one feature
// ✅ GOOD: Minimal dependencies
dependencies:
firebase_analytics: ^10.0.0 # Single analytics solution
shared_preferences: ^2.0.0 # Only local storage
# Before adding ANY package, ask:
# 1. Is it REALLY necessary?
# 2. What data does it collect?
# 3. Is it GDPR compliant?
# 4. Can I do it with native code instead?
2. Consent Banner & Settings
Users must see a clear consent choice when the app first opens, and tracking must remain disabled until consent is given.
🎯 Ask for consent before any tracking begins*. The app must not send analytics, ads, or location data* until the user explicitly accepts*.
// ❌ BAD: Tracking starts immediately without consent
class AppStartup {
void initApp() {
AnalyticsService().enable(); // ❌ Tracking before user consents
}
}
// ✅ GOOD: Show consent banner before enabling tracking
class AppStartup {
Future<void> initApp(BuildContext context) async {
final consent = await ConsentManager().getAnalyticsConsent();
if (!consent) {
showConsentDialog(context); // ask user first
} else {
AnalyticsService().enable();
}
}
}
// ✅ GOOD: Allow users to change consent later in Settings
class SettingsPage extends StatelessWidget {
final ConsentManager consentManager;
SettingsPage(this.consentManager);
@override
Widget build(BuildContext context) {
return SwitchListTile(
title: Text('Allow Analytics'),
value: consentManager.analyticsConsent,
onChanged: (value) {
consentManager.setAnalyticsConsent(value);
},
);
}
}
3. IP Address & Location Data
IP address and precise location (latitude/longitude) are considered personal data under GDPR.
🎯 Only collect or send them if strictly necessary, and always anonymize or ask for explicit consent!
// ❌ BAD: Always sending full IP and exact GPS location
class TrackingService {
void sendData(String ip, double lat, double lon) {
api.post('/track', {
'ip': ip, // ❌ full IP
'latitude': lat, // ❌ exact location
'longitude': lon,
});
}
}
// ✅ GOOD: Anonymize IP and use coarse location
class TrackingService {
void sendData(String ip, double lat, double lon) {
final anonymizedIp = ip.replaceAll(RegExp(r'\.\d+$'), '.0'); // mask last octet
final roundedLat = double.parse(lat.toStringAsFixed(2)); // ~1km accuracy
final roundedLon = double.parse(lon.toStringAsFixed(2));
api.post('/track', {
'ip': anonymizedIp, // ✅ anonymized
'latitude': roundedLat,
'longitude': roundedLon,
});
}
}
// ✅ BEST: Ask for explicit consent before collecting
if (userConsentManager.locationConsent) {
TrackingService().sendData(ip, lat, lon);
}
4. Data Retention for Analytics & Logs
Keep raw, user-level data for the shortest possible time; keep only aggregates longer.
🎯 Data Retention Rules:
- Keep raw identifiable data for no longer than 14–30 days*.
- Keep aggregated or anonymized data for 12–24 months*.
- Keep consent records and audit logs for longer periods, as evidence of compliance.
Examples (Sentry + Amplitude)
- Sentry = Crash reporting (functional, needed for app quality)
- Amplitude = Analytics (optional, needs consent)
Future<void> initApp(BuildContext context) async {
// 1. Ask for consent FIRST
final consent = await askConsentDialog(context);
// 2. Sentry: Enable but scrub ALL PII
await SentryFlutter.init((o) {
o.dsn = 'YOUR_SENTRY_DSN';
o.sendDefaultPii = false; // ✅ No PII
o.beforeSend = (e, {hint}) => e.copyWith(
user: null, // ✅ Remove user info
request: null, // ✅ Remove request data
);
});
// 3. Amplitude: ONLY if user consented
if (consent) {
Amplitude(Configuration(
apiKey: 'YOUR_AMPLITUDE_KEY',
trackingOptions: TrackingOptions(
ipAddress: false, // ✅ No IP
adid: false, // ✅ No ad ID
idfv: false, // ✅ No device ID
latLng: false, // ✅ No GPS coordinates
city: false, // ✅ No city
region: false, // ✅ No region
country: false, // ✅ No country
carrier: false, // ✅ No carrier
dma: false, // ✅ No DMA
),
));
} else {
// ✅ No Amplitude = No tracking
print('User rejected analytics consent');
}
}
Key Points:
- Sentry enabled for crash reports (legitimate interest)
- BUT all PII removed from Sentry
- Amplitude ONLY enabled if user consents
- Even with consent, ALL tracking options disabled
- No IP, no location, no device IDs sent anywhere
References:
Thank you for reading, I hope it is helpful. If you have any questions, feel free to ask on LinkedIn.