Privacy & consent
Gate the Grovs Flutter SDK on user consent, and see what it collects.
Gate collection on user consent, and declare what the native SDKs collect.
Gating collection on consent
A disabled SDK collects nothing. The plugin configures the native SDKs at launch, before any Dart runs, so the launch state must come from the native config files. Set GrovsEnabled to false in Info.plist and grovs_enabled to false in AndroidManifest.xml:
<!-- ios/Runner/Info.plist -->
<key>GrovsEnabled</key>
<false/><!-- android/app/src/main/AndroidManifest.xml, inside <application> -->
<meta-data
android:name="grovs_enabled"
android:value="false" />Toggle it from Dart as consent changes:
import 'package:grovs_flutter_plugin/grovs.dart';
final grovs = Grovs();
Future<void> userDidChangeConsent(bool granted) async {
await ConsentStore.save(granted); // your own storage
await grovs.setSDK(granted);
}The SDK doesn't persist the flag. Store the decision yourself and call setSDK(true) on every launch once you've restored a positive one:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
if (await ConsentStore.hasConsent()) {
await Grovs().setSDK(true);
}
runApp(const App());
}Leaving the config key at true and calling setSDK(false) in main() is too late. The native SDK is already configured, authenticating, and on a first launch may have read the clipboard. Start disabled through the config key.
setSDK with the current state does nothing. On Android, every Flutter engine in the process shares the consent state, since the native SDK is configured once per process.
While the SDK is disabled
The native SDKs behave as in a native app (full lists: iOS, Android):
-
Nothing leaves the device: no authentication, device lookup, events, screen views or purchases.
-
generateLinkthrows aGrovsException. -
Changes to the user identifier, attributes, push token and screen aliases are held as the latest values.
-
Already-queued events and purchases stay on the device.
-
No in-app messages show, and push taps aren't reported.
The plugin adds one rule: deep links and in-app events arriving while disabled aren't delivered on onDeeplinkReceived or onInAppMessage, and buffered ones are discarded. linkDetails throws SDK_DISABLED and handleUrl returns false.
Enabling again
setSDK(true) authenticates if needed and sends everything held or queued.
The plugin also replays the link that launched the app while disabled: on Android by re-running the launch intent through Grovs.onStart, on iOS by handing the universal link or URL back to the SDK. Only the most recent launch link is kept; it arrives on onDeeplinkReceived once resolved.
On iOS, the plugin drops a link lookup in flight when setSDK(false) is called. If consent returns before that lookup completes, the native SDK may still deliver it.
What the SDK collects
The plugin collects nothing itself; the native SDK does. Full tables: iOS and Android. Summary:
| Data | When |
|---|---|
| App version and build, bundle ID or package name, device model, user agent, screen size, time zone, preferred language | When the SDK authenticates and looks up the device |
| A device identifier (IDFV and a keychain identifier on iOS; the Android ID and the Play install referrer on Android) | Same |
| User identifier, user attributes, push token | Only if you set them |
| Installs, opens, time spent, reactivations | Automatically |
| Custom events and screen views | When you call track or trackScreenView, and through Grovs.navigatorObserver |
| Purchases | When you call logInAppPurchase or logCustomPurchase |
| Push campaign opens | When the user taps a Grovs push |
| In-app message views, clicks, dismissals and answers | When a message shows and the user acts on it |
| Clipboard contents | Read once on the first launch after install, when your project has recent copy-to-clipboard clicks and the clipboard holds a web URL. Sent only if it's a link on a Grovs host or one of your GrovsClipboardDomains / grovs_clipboard_domains. See clipboard-assisted deferred deep linking |
Neither native SDK reads the advertising identifier. iOS doesn't need App Tracking Transparency.
Store privacy declarations
- App Store: the iOS SDK ships a privacy manifest; your App Privacy answers must cover what it collects. The plugin adds its own manifest, which declares nothing; since 3.1 it's included in CocoaPods builds as well as Swift Package Manager builds. See iOS privacy manifest and App Store privacy details.
- Google Play: your Data safety form must cover the same. See Google Play data safety.
Anything in user attributes or event properties (an email or name, say) is data you collect too and needs its own declaration.
Logs
setDebugLevel accepts 'info' or 'error'; anything else is treated as 'error'. Use 'info' while integrating:
await Grovs().setDebugLevel('info');Where logs appear and how to filter them: iOS logs, Android logs.