Collecting analytics or behavioral data from users in the EU without a consent UI violates GDPR Art. 7 (conditions for consent) and the ePrivacy Directive Art. 5(3). In California, CCPA §1798.100 requires disclosure and opt-out rights before selling or sharing personal information. Apps that initialize analytics SDKs unconditionally — before checking persisted consent — are non-compliant regardless of whether they display a banner later. Google Play's Data Safety section and Apple's App Store privacy label are public-facing disclosures; regulators and users can compare them against actual SDK behavior. A consent UI that does not gate SDK initialization is cosmetically compliant but legally exposed: the DPA enforcement record consistently shows that the banner must actually stop data flow, not just record a preference.
High because analytics SDK initialization before consent is persisted and checked is a direct GDPR Art. 7 violation, and regulators have fined companies specifically for consent UIs that do not actually stop data collection.
Gate all analytics SDK initialization behind a persisted consent check. The SDK must not call any tracking method until userConsent === 'true' is read from storage.
import AsyncStorage from '@react-native-async-storage/async-storage'
import { initAnalytics, disableAnalytics } from './analytics'
export async function bootstrapWithConsent() {
const consent = await AsyncStorage.getItem('gdpr_consent')
if (consent === 'accepted') {
initAnalytics({ sampling: 1.0 })
} else {
// Do NOT initialize — SDK stays silent until user consents
disableAnalytics()
}
}
// Call on app start, before any navigation
bootstrapWithConsent()
The consent UI must offer a real decline option — "Accept" and "Settings" are not equivalent to "Accept" and "Decline". Store the user's choice and re-check on every app launch.
ID: mobile-permissions-privacy.privacy-compliance.consent-ui
Severity: high
What to look for: Search for consent-related UI components (banners, modals, settings toggles). Check whether consent state is saved (look for AsyncStorage or SecureStore calls with consent-related keys). Enumerate all analytics/tracking SDK initializations and verify they check consent state before activating.
Pass criteria: If the app serves EU or US users: at least 1 consent UI is displayed (on launch or accessible in settings) that allows users to accept/reject data collection. Consent state is persisted via storage. Analytics/tracking respects the persisted consent value.
Fail criteria: App serves regulated regions but no consent UI is present. OR consent UI exists but user choice is not persisted, allowing the app to ignore user preferences. Do NOT pass when consent is collected but analytics SDK is initialized unconditionally regardless of consent.
Skip (N/A) when: App has no data collection, tracking, or analytics SDKs, or explicitly targets only non-regulated regions.
Detail on fail: Quote the analytics initialization code. Example: "No consent banner for GDPR/CCPA — app collects analytics but does not ask permission" or "Consent modal shown once but never respects user choice (always tracks)"
Remediation: Display a clear consent UI on first app load for regions that require it:
// Minimal consent example
function ConsentBanner() {
const [consentGiven, setConsentGiven] = useState(null)
useEffect(() => {
// Check if user has already consented
AsyncStorage.getItem('userConsent').then(consent => {
if (consent === null) {
setConsentGiven(false) // Show banner
} else {
setConsentGiven(JSON.parse(consent))
}
})
}, [])
const handleConsent = async (accepted) => {
await AsyncStorage.setItem('userConsent', JSON.stringify(accepted))
setConsentGiven(accepted)
if (accepted) {
// Enable analytics, tracking, etc.
}
}
if (consentGiven === false) {
return (
<Modal>
<Text>We use analytics to improve your experience</Text>
<Button title="Accept" onPress={() => handleConsent(true)} />
<Button title="Decline" onPress={() => handleConsent(false)} />
</Modal>
)
}
return null
}