grovs_flutter_plugin 3.0.1
grovs_flutter_plugin: ^3.0.1 copied to clipboard
Flutter plugin for Grovs SDK - Deep linking, smart links, user messaging, and campaign tracking for mobile apps.
Deep linking, attribution, and smart links for Flutter.
Part of the Grovs open-source mobile linking platform.
Quick Start · API Reference · Full Docs
The Grovs Flutter SDK provides deep linking, app links, universal links, link generation, in-app messaging, revenue tracking, and attribution for your Flutter apps. It wraps the native iOS and Android SDKs.
Features #
- Deep linking & universal links — route users to the right in-app screen, even after install
- Smart link generation — create trackable links with metadata, custom redirects, and UTM parameters
- In-app messaging — display messages and announcements from the Grovs dashboard
- Push notifications — receive push notifications for dashboard-sent messages
- Revenue tracking — log App Store, Google Play, and custom purchases with automatic attribution
- Analytics: track custom events and screen views, with automatic screen tracking via
Grovs.navigatorObserver - User identity — attach user IDs and attributes for analytics and segmentation
- Self-hosting support — point the SDK at your own backend
- Auto-configuration — platform config via
AndroidManifest.xmlandInfo.plist - Consent control: start disabled and enable the SDK once the user agrees
- Deferred deep linking: resolve links tapped before installing via fingerprinting and clipboard
Requirements #
- Flutter 3.3.0+
- Dart 3.9.2+
- iOS 13.0+
- Android API 24+ (Android 7.0)
Installation #
Add the dependency to your pubspec.yaml:
dependencies:
grovs_flutter_plugin: ^3.0.0
Then run:
flutter pub get
Platform Setup #
Android #
1. Add configuration to AndroidManifest.xml
Add the Grovs API key and environment setting inside the <application> tag in android/app/src/main/AndroidManifest.xml:
<application>
<meta-data
android:name="grovs_api_key"
android:value="YOUR_API_KEY" />
<meta-data
android:name="grovs_use_test_environment"
android:value="true" /> <!-- Set to false for production -->
<!-- Optional: Custom base URL for self-hosted backends -->
<meta-data
android:name="grovs_base_url"
android:value="https://your-domain.com" />
<!-- Optional: start the SDK disabled until the user gives consent (default true) -->
<meta-data
android:name="grovs_enabled"
android:value="false" />
<!-- Optional: extra hosts accepted for clipboard deferred deep linking, comma separated -->
<meta-data
android:name="grovs_clipboard_domains"
android:value="links.example.com,promo.example.com" />
</application>
2. Add intent filters
Add these to your main activity for deep link handling:
<activity android:name=".MainActivity">
<!-- Custom URL scheme -->
<intent-filter>
<data android:scheme="your_app_scheme" android:host="open" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<!-- App links (production) -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="your_app_host" />
</intent-filter>
<!-- App links (test) -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="your_app_test_host" />
</intent-filter>
</activity>
iOS #
1. Add configuration to Info.plist
Add to ios/Runner/Info.plist:
<key>GrovsApiKey</key>
<string>YOUR_API_KEY</string>
<key>GrovsUseTestEnvironment</key>
<true/> <!-- Set to <false/> for production -->
<!-- Optional: Custom base URL for self-hosted backends -->
<key>GrovsBaseURL</key>
<string>https://your-domain.com</string>
<!-- Optional: start the SDK disabled until the user gives consent (default true) -->
<key>GrovsEnabled</key>
<false/>
<!-- Optional: extra hosts accepted for clipboard deferred deep linking -->
<key>GrovsClipboardDomains</key>
<array>
<string>links.example.com</string>
<string>promo.example.com</string>
</array>
2. Configure URL schemes
Add custom URL scheme support to Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>your_app_scheme</string>
</array>
</dict>
</array>
3. Configure Associated Domains
- Open your project in Xcode
- Select your app target → Signing & Capabilities tab
- Click + Capability → add Associated Domains
- Add
applinks:your_app_hostandapplinks:your_app_test_host
Quick Start #
Initialize and configure #
import 'package:grovs_flutter_plugin/grovs.dart';
final grovs = Grovs();
// Optional: enable debug logging
await grovs.setDebugLevel('info');
// Optional: set user identity for analytics
await grovs.setUserIdentifier('user_id_from_your_app');
await grovs.setUserAttributes({
'name': 'John Doe',
'plan': 'premium',
});
Handle deep links #
Subscribe to the onDeeplinkReceived stream to handle incoming deep links:
import 'dart:async';
import 'package:grovs_flutter_plugin/grovs.dart';
StreamSubscription<DeeplinkDetails>? _subscription;
@override
void initState() {
super.initState();
_subscription = grovs.onDeeplinkReceived.listen((details) {
final link = details.link;
final payload = details.data;
final tracking = details.tracking;
print('Opened from: $link');
// Route the user based on payload
if (payload?['screen'] == 'product') {
navigateToProduct(payload?['productId']);
}
});
}
@override
void dispose() {
_subscription?.cancel();
super.dispose();
}
Listen for SDK errors #
import 'dart:async';
import 'package:grovs_flutter_plugin/models/grovs_link.dart';
StreamSubscription<GrovsError>? _errorSubscription;
_errorSubscription = grovs.onError.listen((error) {
print('Grovs ${error.code.nativeName}: ${error.message}');
});
Emitted for authentication, network, event delivery, and link generation failures. iOS only for now; Android emits nothing. The latest 20 errors raised before subscribing are buffered. Cancel the subscription when disposing your widget.
Consent #
The SDK is enabled by default and authenticates on launch. For apps that need user consent first:
- Set
GrovsEnabledtofalse(iOS) andgrovs_enabledtofalse(Android). The SDK is configured but makes no network calls, tracks nothing, and does not read the clipboard. - Once the user agrees, call
setSDK(true).
await grovs.setSDK(true); // user gave consent
await grovs.setSDK(false); // user withdrew consent
The SDK does not persist consent. Store the user's choice in your app and apply it on every launch. Start with the config key set to false and enable the SDK after restoring consent. Calling setSDK(false) from Dart cannot prevent native startup work when the config key is true.
While disabled, user identifier and attribute changes are kept and synced once enabled. A deep link that opened the app while disabled is processed and delivered on onDeeplinkReceived after setSDK(true).
Deferred deep linking via clipboard #
When a user taps a link with copyToClipboard enabled and does not have the app installed, the preview page copies the link to the clipboard. On the first launch after install the native SDK checks the clipboard once to resolve that link:
- It runs only once per install, and only after the backend confirms the project had recent clipboard-enabled clicks.
- The clipboard is read only when its content looks like a URL. iOS may show the system paste notice.
- Accepted hosts are the Grovs link domains plus any
GrovsClipboardDomains/grovs_clipboard_domainsentries. Anything else is ignored and never leaves the device. - There is no separate opt-out. Starting the SDK disabled (see Consent) prevents the read.
Set the flags per link with copyToClipboardIos and copyToClipboardAndroid on GenerateLinkParams, or leave them null to use the project default.
Link Generation #
Create smart links with metadata, payload data, and tracking parameters:
import 'package:grovs_flutter_plugin/grovs.dart';
import 'package:grovs_flutter_plugin/models/grovs_link.dart';
try {
final link = await grovs.generateLink(
GenerateLinkParams(
title: 'Check out this product',
subtitle: 'Limited time offer',
imageURL: 'https://example.com/image.jpg',
data: {
'screen': 'product',
'productId': '12345',
},
tags: ['promotion', 'share'],
tracking: TrackingParams(
utmCampaign: 'spring_sale',
utmSource: 'in_app',
utmMedium: 'share_button',
),
),
);
print('Generated: $link');
} on GrovsException catch (e) {
print('Error: ${e.message}');
}
Custom redirects #
Override where a link sends users on each platform:
final link = await grovs.generateLink(
GenerateLinkParams(
title: 'Special offer',
data: {'promoId': 'summer25'},
customRedirects: CustomRedirects(
ios: CustomLinkRedirect(url: 'https://example.com/ios-promo'),
android: CustomLinkRedirect(url: 'https://example.com/android-promo'),
desktop: CustomLinkRedirect(url: 'https://example.com/desktop-promo', openAppIfInstalled: false),
),
),
);
Share dialog #
Launch the platform share sheet after generating a link:
import 'package:share_plus/share_plus.dart';
final link = await grovs.generateLink(
GenerateLinkParams(title: 'Share this', data: {'itemId': 'abc'}),
);
Share.share(link);
Messages #
If console messages have automatic display enabled in your dashboard, they will appear in your app without any additional integration.
Push notifications #
Pass the device token to receive push notifications for dashboard-sent messages:
import 'package:firebase_messaging/firebase_messaging.dart';
// Get and set the token
final token = await FirebaseMessaging.instance.getToken();
if (token != null) {
await grovs.setPushToken(token);
}
// Listen for token refreshes
FirebaseMessaging.instance.onTokenRefresh.listen((newToken) {
grovs.setPushToken(newToken);
});
Upload your Firebase or APNs credentials in the Grovs dashboard under your platform's push notification settings.
Push notifications require a physical device. They do not work in the iOS Simulator.
Revenue Tracking #
Revenue tracking is currently in beta.
Setup #
- Enable revenue tracking in the Grovs dashboard under Settings → Revenue Tracking
- Configure platform notifications:
- Android — Set up Google Play Real-Time Developer Notifications
- iOS — Configure App Store Server Notifications in App Store Connect
Platform store purchases #
// iOS: pass the StoreKit transaction ID as a string
// Android: pass the purchase originalJson string
await grovs.logInAppPurchase('transaction_id_or_json');
The SDK automatically extracts price, currency, and product info. Duplicates are filtered.
Custom purchases #
import 'package:grovs_flutter_plugin/models/grovs_link.dart';
await grovs.logCustomPurchase(
type: TransactionType.buy,
priceInCents: 999, // $9.99
currency: 'USD',
productId: 'premium_monthly',
);
Use .cancel and .refund transaction types for cancellations and refunds. For store purchases, these are detected automatically via platform server notifications.
Analytics #
Track custom events, screen views, and tags. Lifecycle events
(install, app_open, time_spent, reactivation) are recorded
automatically by the native SDK — no setup required.
Custom events #
await Grovs().track(
'signup_completed',
properties: {'plan': 'pro'},
tags: ['onboarding'],
);
Event names must not be empty or one of the reserved names: view, open,
install, reinstall, app_open, time_spent, reactivation,
user_referred, custom, screen_view. Properties are capped at 8 KB and
tags at 20 (enforced natively).
Global tags #
await Grovs().setGlobalTags(['premium', 'beta']); // pass null to clear
Automatic screen tracking #
Add the observer to your MaterialApp:
MaterialApp(
navigatorObservers: [Grovs.navigatorObserver],
// ...
);
Screen names come from each route's RouteSettings.name. Unnamed routes fall
back to the route's runtime type, which is obfuscated in release builds — name
your routes or pass a screenNameExtractor to a custom
GrovsNavigatorObserver for stable names.
Manual screen views #
await Grovs().trackScreenView('Checkout', properties: {'step': 2});
Screen aliases #
Map raw screen names to friendly dashboard names:
await Grovs().setScreenAliases({'/p': 'Product', '/c': 'Cart'});
API Reference #
Properties #
| Property | Type | Description |
|---|---|---|
onError |
Stream<GrovsError> |
Native SDK errors (iOS only for now) |
onDeeplinkReceived |
Stream<DeeplinkDetails> |
Stream of deep link events |
Key Methods #
| Method | Description |
|---|---|
setDebugLevel(level) |
Set logging level ('info', 'error') |
setPushToken(token) |
Set FCM/APNs push token |
setUserIdentifier(identifier) |
Set user ID for dashboard and reports |
setUserAttributes(attributes) |
Set user attributes for analytics |
setSDK(enabled) |
Enable or disable the SDK at runtime (consent) |
generateLink(params) |
Generate a smart link |
logInAppPurchase(transactionId) |
Log a store purchase |
logCustomPurchase(type, priceInCents, currency, productId, startDate) |
Log a custom purchase |
track(name, {properties, tags}) |
Log a custom event |
trackScreenView(screenName, {properties}) |
Log a screen view manually |
setGlobalTags(tags) |
Set tags applied to all future events |
setScreenAliases(aliases) |
Map raw screen names to friendly dashboard names |
Grovs.navigatorObserver |
NavigatorObserver for automatic screen tracking |
Full API reference: docs.grovs.io/docs/sdk/flutter/api-reference
Example App #
A demo project is included in the example/ directory.
Migration Guides #
Documentation #
Full documentation at docs.grovs.io.
Support #
For technical support and inquiries, contact support@grovs.io.
License #
This project is licensed under the MIT License — see LICENSE for details.