Flutter SDK
The Flutter plugin is currently in beta. The Dart API surface and the underlying MethodChannel wire contract may change between 0.x releases. Pin to an exact version in your pubspec.yaml (linksense: 0.1.0) and review the changelog before upgrading. We commit to API stability at 1.0.
Overview
The LinkSense Flutter plugin is a thin Dart facade over the native iOS and Android SDKs. It does no attribution logic itself — every public method is a typed passthrough that invokes a native bridge over MethodChannel + EventChannel. You get the same capabilities as the native SDKs from a single Dart API:
- Install-time attribution via the underlying iOS and Android SDKs.
- Deferred deep linking — exposed as a Dart
Stream<Uri?>. - Universal Link / App Link forwarding — Android App Links auto-forward via the plugin's
ActivityAwareintegration; iOS Universal Links flow throughhandleUniversalLink(). - Link CRUD — async
Future-returning create / read / update / delete.
Prerequisites
- Flutter 3.24 or later (Swift Package Manager support)
- iOS 14.0+ deployment target, Android
minSdk24 - An SDK key from your LinkSense project (Project Settings → SDK Keys)
- Universal Links / App Links configured for your project subdomain (see iOS Deep Linking and Android Deep Linking)
Installation
Add the plugin from pub.dev:
flutter pub add linksensePlatform Setup
iOS — the plugin pulls the native iOS SDK over Swift Package Manager. Make sure SPM is enabled in your Flutter toolchain (default in Flutter 3.27+):
flutter config --enable-swift-package-managerAndroid — the plugin resolves the native Android SDK from Maven Central automatically; no extra Gradle changes are required beyond having mavenCentral() in your repositories block.
On Android, the plugin auto-forwards ACTION_VIEW intents to the SDK via ActivityAware + NewIntentListener. You do not need to modify MainActivity.kt.
Configuration
Call configure, subscribe to the deferred deep link stream, then call start — typically in your main() before runApp:
import 'package:linksense/linksense.dart'; Future<void> bootstrap() async { await LinkSense.instance.configure(const LinkSenseConfiguration( sdkKey: 'ls_sdk_<project>_<random>', enableClipboard: false, )); LinkSense.instance.deferredDeepLinks.listen((uri) { if (uri != null) appRouter.handle(uri); }); await LinkSense.instance.start();}Configuration fields:
| Field | Type | Required | Description |
|---|---|---|---|
sdkKey | String | Yes | Your project SDK key (format: ls_sdk_<project>_<random>). Generate one in Project Settings → SDK Keys. |
baseUrl | String? | No | Override the LinkSense API base URL. Used in local development; defaults to production. |
enableClipboard | bool | No | iOS-only. Read the pasteboard on first launch for clipboard-based deferred attribution. Default false. |
enableInstallReferrer | bool | No | Android-only. Use Google Play Install Referrer for high-confidence attribution. Default true. |
enableFingerprint | bool | No | Probabilistic fingerprint matching when no install referrer or click id is available. Default true. |
Handling Deferred Deep Links
Deferred deep links are exposed as a Dart Stream<Uri?> on LinkSense.instance.deferredDeepLinks. Subscribe before calling start() to avoid missing the first emission. The stream emits both deferred (cold-start) and Universal Link (warm-path) URLs.
Once the SDK resolves the install against the server, the original click URL is pushed onto the stream. If no click matched, a single null emission signals attribution completed empty.
Forwarding Universal Links
On iOS, forward any NSUserActivity-style URL you receive from your routing layer through handleUniversalLink. The resolved URI is delivered through the same deferredDeepLinks stream.
// iOS only. No-op on Android.await LinkSense.instance.handleUniversalLink(incomingUri);On Android, this method is a no-op — App Link intents are auto-forwarded by the plugin's native side, so calling it from cross-platform code is harmless.
Link CRUD
The SDK exposes createLink, getLink, updateLink, and deleteLink as Future-returning methods. Any failure is thrown as a typed LinkSenseException — see Error Handling below.
final link = await LinkSense.instance.createLink( CreateLinkOptions( name: 'Summer sale', desktopUrl: Uri.parse('https://example.com/sale'), iosFallbackUrl: Uri.parse('https://apps.apple.com/app/id123456789'), androidFallbackUrl: Uri.parse( 'https://play.google.com/store/apps/details?id=com.example.myapp', ), ),);print('Created: ${link.url}');To update a field, set it on UpdateLinkOptions. To explicitly clear a field server-side, use the linkSenseClear sentinel. Omitting a field entirely leaves the server value untouched.
await LinkSense.instance.updateLink( 'ln_abc123', UpdateLinkOptions( name: 'Renamed link', iosFallbackUrl: linkSenseClear, // explicitly null on the server // androidFallbackUrl omitted — leaves the server value untouched ),);Error Handling
Every public method translates native PlatformExceptions into a sealed LinkSenseException hierarchy. Use Dart's typed on … catch clauses:
try { final link = await LinkSense.instance.getLink('ln_abc123'); // use link} on LinkSenseNotConfigured { print('Call configure() first');} on LinkSenseInvalidInput catch (e) { print('Field errors: ${e.fieldErrors}');} on LinkSenseRateLimited catch (e) { print('Retry after ${e.retryAfterSeconds}s');} on LinkSenseException catch (e) { print('SDK error: ${e.code}');}Typed subclasses: LinkSenseNotConfigured, LinkSenseInvalidSdkKey, LinkSenseNetworkException, LinkSenseDecodeFailure, LinkSenseUnauthorized, LinkSenseForbiddenCrossScope, LinkSenseForbiddenField, LinkSenseInvalidInput (carries fieldErrors), LinkSenseNotFound, LinkSenseCustomPathExists, LinkSenseRateLimited (carries retryAfterSeconds), LinkSenseServerException (catch-all).
Example & Resources
The pub.dev package includes a working Flutter sample under example/ covering configuration, deferred deep link handling, and link CRUD on both platforms.