Quick start
Install and configure the Grovs Flutter SDK in your app.
Install the plugin, add the native configuration and open your first link on a device. Takes about fifteen minutes.
Copy one prompt into Claude Code, Cursor or Copilot and it does these steps in your project.
Add Grovs deep linking to this Flutter app (example app: Medly). 1. Add grovs_flutter_plugin: ^3.1.1 to pubspec.yaml and run flutter pub get. It needs Flutter 3.38.0 or later. 2. Android: in AndroidManifest.xml, add the grovs_api_key and grovs_use_test_environment meta-data inside the application element. Add intent filters to the launcher activity: the custom URL scheme and autoVerify App Links for the production and test hosts. Add flutter_deeplinking_enabled=false meta-data to that activity. 3. iOS: in Info.plist, add GrovsApiKey, GrovsUseTestEnvironment, FlutterDeepLinkingEnabled=false and the URL scheme from the Grovs dashboard. Add the Associated Domains capability with applinks: entries for the production and test hosts. 4. Read the API key from the existing secrets setup (env GROVS_API_KEY); don't commit it. Use the test environment only in debug builds. 5. Subscribe to Grovs().onDeeplinkReceived in the root widget and route DeeplinkDetails.data to the existing navigation. 6. Build both platforms, then list what you changed. Docs: https://docs.grovs.io/docs/sdk/flutter/quick-start.md
Upgrading from 3.0? See the 3.1 upgrade guide. From 1.x, start with the 3.0 upgrade guide.
Requirements
- Flutter 3.38.0+, Dart 3.9.2+
- iOS 13.0+
- Android 7.0 (API 24)+. The launcher activity can extend
FlutterActivity(the default) orFlutterFragmentActivity. - Your bundle ID, package name and SHA-256 signing fingerprint registered in the Grovs dashboard. See How to Get the Package Name and How to Get the SHA-256 Fingerprint
Installation
Add to pubspec.yaml:
dependencies:
grovs_flutter_plugin: ^3.1.1flutter pub getThe plugin pulls in the native SDKs (Grovs from 3.1.0 on iOS, io.grovs:Grovs:3.1.0 on Android). Don't add them to your app separately. On iOS it builds with Swift Package Manager when Flutter's SPM support is on, and with CocoaPods (Grovs ~> 3.1) otherwise.
Configuration
There's no configure call in Dart. The plugin reads native config files and configures the SDK at launch, before any Dart runs.
| Info.plist key | AndroidManifest.xml meta-data name | Default | Description |
|---|---|---|---|
GrovsApiKey (String) | grovs_api_key (string) | required | Project API key from the Grovs dashboard. If missing, iOS doesn't configure the SDK at all; Android passes an empty string. |
GrovsUseTestEnvironment (Boolean) | grovs_use_test_environment (boolean) | false | true targets the test environment, false production. Match your API key's environment. |
GrovsBaseURL (String) | grovs_base_url (string) | Grovs cloud | Domain of a self-hosted backend, for example https://sdk.example.com. |
GrovsClipboardDomains (Array of String) | grovs_clipboard_domains (comma-separated string) | none | Extra link hosts accepted by clipboard-assisted deferred deep linking. Grovs-owned hosts are always accepted. |
GrovsEnabled (Boolean) | grovs_enabled (boolean) | true | Whether the SDK may collect and send anything this launch. Set false and call setSDK(true) after consent. See Privacy & Consent. |
The plugin always turns native automatic screen tracking off. Screen views come from Grovs.navigatorObserver; see Events & Screen Tracking.
Android setup
1. Add configuration to AndroidManifest.xml
Inside <application> 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" />
<!-- Optional: custom base URL for self-hosted backends -->
<meta-data
android:name="grovs_base_url"
android:value="https://sdk.example.com" />
<!-- Optional: start 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 -->
<meta-data
android:name="grovs_clipboard_domains"
android:value="links.example.com,promo.example.com" />
</application>The plugin configures the SDK once per process when the first Flutter engine attaches. It follows the host activity's own lifecycle to call Grovs.onStart, and forwards onNewIntent from the engine's activity. MainActivity needs no changes.
2. Add intent filters
Add these to your launcher activity:
<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>3. Turn off Flutter's built-in deep linking
The plugin hands every link to the SDK and leaves it for other plugins too, so Flutter's own deep linking would also open Grovs links as routes. Add this inside the launcher activity:
<meta-data android:name="flutter_deeplinking_enabled" android:value="false" />iOS setup
1. Add configuration to Info.plist
Add to ios/Runner/Info.plist:
<key>GrovsApiKey</key>
<string>YOUR_API_KEY</string>
<key>GrovsUseTestEnvironment</key>
<true/>
<!-- Optional: custom base URL for self-hosted backends -->
<key>GrovsBaseURL</key>
<string>https://sdk.example.com</string>
<!-- Optional: start 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>The plugin registers as an application delegate and a scene delegate, so launches, universal links and URL opens reach the SDK with no AppDelegate.swift or SceneDelegate changes. It never claims a link, so other plugins (login callbacks, say) still receive theirs. It also sets itself as Grovs.delegate and Grovs.inAppDelegate. Don't assign your own delegates natively, or deep links and in-app events stop reaching Dart.
2. Configure URL schemes
Add your URL scheme 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
ios/Runner.xcworkspacein Xcode. - Select the Runner target, then Signing & Capabilities.
- Click + Capability and add Associated Domains.
- Add
applinks:your_app_hostandapplinks:your_app_test_host.
4. Turn off Flutter's built-in deep linking
Without this, Flutter's own deep linking also opens Grovs links as routes and can send links that have no matching route to Safari. Add to Info.plist:
<key>FlutterDeepLinkingEnabled</key>
<false/>5. App lifecycle
Nothing to add. The plugin works with the UIScene lifecycle (FlutterSceneDelegate, the default for new Flutter apps) and with AppDelegate-only apps. If you wrote scene delegate code that calls Grovs.handleSceneDelegate, remove it, or every link is delivered twice. If your scene delegate doesn't extend FlutterSceneDelegate, follow Flutter's UIScene migration guide so Flutter passes scene events to plugins.
For push campaigns, set the notification center delegate in AppDelegate.swift. See Push notifications.
Basic usage
import 'package:grovs_flutter_plugin/grovs.dart';
final grovs = Grovs();
// Optional: verbose logging while integrating ('info' or 'error')
await grovs.setDebugLevel('info');
// Optional: identify the user
await grovs.setUserIdentifier('user-123');
await grovs.setUserAttributes({
'name': 'Jane Doe',
'plan': 'premium',
});Every method returns a Future and throws GrovsException when the native call fails. Only getPlatformVersion lets a raw PlatformException through. grovs.dart also exports the in-app message types. The link and error models (GenerateLinkParams, DeeplinkDetails, GrovsLinkDetails, GrovsException and the rest) need a second import:
import 'package:grovs_flutter_plugin/models/grovs_link.dart';Next steps
- Handle incoming deep links →
- Generate smart links →
- Receive push campaigns →
- Control in-app messages →
- Track events and screens →
- Gate collection on user consent →
See also
- Integrating a pub.dev Package: add the package from pub.dev
- How to Add an Associated Domain: Xcode steps in detail
- How to Add a New Intent Filter: Android intent filters in detail
- How to Get the SHA-256 Fingerprint: fingerprints for App Links verification
- Deep link not opening the app: links open the browser instead of the app
- SDK configuration issues: common setup and initialization problems