Flutter SDK Setup Guide
The Linkzly Flutter SDK is a Dart bridge over the native iOS and Android Linkzly SDKs. It exposes the full attribution and deep-linking surface — `configure`, `
Flutter SDK Setup Guide
The Linkzly Flutter SDK is a Dart bridge over the native iOS and Android Linkzly SDKs. It exposes the full attribution and deep-linking surface — configure, trackInstall, trackOpen, trackEvent, trackPurchase, deep link / universal link streams, affiliate attribution, push, SKAdNetwork, and gaming tracking — through a single idiomatic Dart API. The plugin does not reimplement attribution logic; it delegates to the platform SDKs.
The package is linkzly_flutter_sdk, distributed from GitHub.
Prerequisites
Before integrating, set up your app in the Linkzly Console:
- Go to Dashboard > Apps and click "Register App".
- Enter your iOS Bundle ID and Team ID, and/or Android Package Name + SHA-256 fingerprints.
- Choose a verification method (Hosted is recommended for a quick start).
- Copy your SDK Key from the post-creation wizard, or from Manage App > Overview > SDK Configuration.
| Requirement | Minimum |
|---|---|
| Flutter | 3.38+ |
| Dart | 3.10+ |
| iOS | 13+ |
| Android | API 21+ |
Your SDK key starts with slk_ and uniquely identifies one app.
Installation
1. Add the Flutter package
The plugin is distributed via GitHub. Pin to a tag in your app's pubspec.yaml:
dependencies:
linkzly_flutter_sdk:
git:
url: https://github.com/Linkzly/linkzly-flutter-sdk.git
ref: v0.1.0
Then run:
flutter pub get
2. iOS — Podfile pin
The native iOS LinkzlySDK is not on the CocoaPods Trunk, so pod install cannot resolve it transitively. Add an explicit pin to ios/Podfile inside target 'Runner':
target 'Runner' do
use_frameworks!
pod 'LinkzlySDK', :git => 'https://github.com/Linkzly/linkzly-ios-sdk.git', :tag => '1.0.3'
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
Then:
cd ios && pod install --repo-update && cd ..
3. Android — JitPack repository
Add JitPack to your app's Android repositories. For Kotlin DSL Flutter projects (android/build.gradle.kts):
allprojects {
repositories {
google()
mavenCentral()
maven("https://jitpack.io")
}
}
If you use dependencyResolutionManagement in android/settings.gradle.kts, add JitPack there instead. For Groovy projects, add it to android/build.gradle with the standard maven { url 'https://jitpack.io' } syntax.
The Flutter plugin already declares the native Android dependency (com.github.Linkzly:linkzly-android-sdk); JitPack is what lets Gradle resolve it.
Platform Setup
iOS — Info.plist
Add to ios/Runner/Info.plist:
<key>NSUserTrackingUsageDescription</key>
<string>We use your data to provide personalized content and improve your app experience.</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>your.bundle.identifier</string>
<key>CFBundleURLSchemes</key>
<array>
<string>your-app-scheme</string>
</array>
</dict>
</array>
If you call updateConversionValue(...) for SKAdNetwork, also add SKAdNetworkItems with the ad-network identifiers your app uses.
iOS — Associated Domains entitlement (Universal Links)
Universal Links do not work without an Associated Domains entitlement. Create or update ios/Runner/Runner.entitlements:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:your-linkzly-domain.example</string>
</array>
</dict>
</plist>
Open ios/Runner.xcworkspace in Xcode → Runner target → Signing & Capabilities → confirm Associated Domains lists your domain, or set CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; in each build configuration. Universal Links also require a valid Apple App Site Association file for your bundle ID and team ID on that domain — Linkzly hosts this for you under Hosted Verification.
Android — AndroidManifest.xml
Add Linkzly deep-link intent filters inside the existing Flutter launcher activity (android/app/src/main/AndroidManifest.xml). Do not create a separate activity. Use launchMode="singleTop":
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Custom URL scheme -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="example" />
</intent-filter>
<!-- App Links (verified HTTPS) -->
<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="example.linkzly.com" />
</intent-filter>
</activity>
Replace example with your URI scheme and example.linkzly.com with the verified Linkzly domain configured in the console. App Links require a valid Digital Asset Links file — Linkzly hosts this for you under Hosted Verification.
Initialization
import 'package:flutter/material.dart';
import 'package:linkzly_flutter_sdk/linkzly_flutter_sdk.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Subscribe BEFORE configure so cold-start events are not missed
Linkzly.instance.deepLinkStream.listen((DeepLinkData data) {
// Route the user based on data.path and data.parameters
});
Linkzly.instance.universalLinkStream.listen((UniversalLinkEvent event) {
// Optional: inspect the raw universal/app link capture
});
await Linkzly.instance.configure(
const LinkzlyConfig(
sdkKey: 'slk_your_key_from_console',
environment: LinkzlyEnvironment.production,
),
);
runApp(const MyApp());
}
Environments are LinkzlyEnvironment.production, .staging, and .development.
Tracking Events
// Custom event
await Linkzly.instance.trackEvent('purchase_completed', parameters: {
'product_id': '12345',
'amount': 29.99,
'currency': 'USD',
});
// Purchase
await Linkzly.instance.trackPurchase(parameters: {
'amount': 9.99,
'currency': 'USD',
'sku': 'premium_monthly',
});
// Install / open — automatic on launch; the explicit form returns the
// deferred-deep-link data when present.
await Linkzly.instance.trackInstall();
await Linkzly.instance.trackOpen();
// Batch
await Linkzly.instance.trackEventBatch([
// platform-shaped event maps
]);
// Manual queue control
await Linkzly.instance.flushEvents();
final pending = await Linkzly.instance.getPendingEventCount();
User Identity
await Linkzly.instance.setUserId('user_12345');
final userId = await Linkzly.instance.getUserId();
final visitorId = await Linkzly.instance.getVisitorId();
await Linkzly.instance.resetVisitorId();
Deep Linking and Universal Links
The plugin exposes two streams:
deepLinkStream— backend-enrichedDeepLinkData(includespath,parameters,smartLinkId,clickId, and deferred deep-link data on fresh installs).universalLinkStream— rawUniversalLinkEventfrom the OS, with the captured URL and query parameters.
final subscription = Linkzly.instance.deepLinkStream.listen((data) {
switch (data.path) {
case '/product': /* route */ break;
case '/promo': /* route */ break;
}
});
// Manual handling
await Linkzly.instance.handleUniversalLink(url);
Cancel subscriptions when no longer needed (subscription.cancel()).
Affiliate Attribution
Affiliate helpers are available via Linkzly.instance and delegate to the native SDKs:
// Capture from a deep link URL — call this from your universal/app link handler
await Linkzly.instance.captureAffiliateAttribution(url);
// At checkout, retrieve the click ID for S2S postback
final clickId = await Linkzly.instance.getAffiliateClickId();
final attribution = await Linkzly.instance.getAffiliateAttribution();
final hasAttribution = await Linkzly.instance.hasAffiliateAttribution();
await Linkzly.instance.clearAffiliateAttribution();
Storage delegates to native — Keychain on iOS, EncryptedSharedPreferences on Android. Data expires after 30 days.
SKAdNetwork and ATT (iOS)
final status = await Linkzly.instance.requestTrackingPermission();
// 'authorized' | 'denied' | 'restricted' | 'notDetermined'
final idfa = await Linkzly.instance.getIdfa();
final attStatus = await Linkzly.instance.getAttStatus();
await Linkzly.instance.updateConversionValue(42); // 0-63
await Linkzly.instance.setAdvertisingTrackingEnabled(false);
final adEnabled = await Linkzly.instance.isAdvertisingTrackingEnabled();
Make sure NSUserTrackingUsageDescription is set in Info.plist. IDFA is only collected after the user grants ATT.
Push Notifications
final success = await Linkzly.instance.initializePush(); // Firebase Cloud Messaging
await Linkzly.instance.disablePush();
initializePush() subscribes the device to a Linkzly FCM broadcast topic via runtime reflection. If Firebase Messaging is not integrated, the call returns false safely. If you use OneSignal, Braze, or another non-FCM provider, skip these methods — the Linkzly backend delivers through whichever provider is configured in the console.
Privacy Controls
await Linkzly.instance.setTrackingEnabled(false);
final isEnabled = await Linkzly.instance.isTrackingEnabled();
The SDK is GDPR- and CCPA-compliant. No PII is collected unless you set it via setUserId().
Gaming Tracking
The plugin exposes the gaming tracking helpers from the native SDKs through the same Linkzly.instance interface. Use them to configure a gaming pipeline, identify players, track events, manage sessions, and set attribution. HMAC request signing is enabled by default for new games — when on, supply the signing secret from Game Settings → SDK Configuration, separate from your SDK Key. The replay window is fixed at 300 seconds, so device clocks must be within 5 minutes of UTC.
See the Android SDK guide and iOS SDK guide for the full list of gaming options and event semantics — the Flutter plugin forwards to those native implementations directly.
API Surface
The full public Dart API includes:
- Lifecycle:
configure,handleUniversalLink,trackInstall,trackOpen - Events:
trackEvent,trackPurchase,trackEventBatch,flushEvents,getPendingEventCount - Users / sessions:
setUserId,getUserId,getVisitorId,resetVisitorId - Privacy:
setTrackingEnabled,isTrackingEnabled,setAdvertisingTrackingEnabled,isAdvertisingTrackingEnabled - iOS:
updateConversionValue,requestTrackingPermission,getAttStatus,getIdfa - Push:
initializePush,disablePush - Affiliate attribution helpers
- Gaming tracking helpers
- Debug batching helpers
Troubleshooting
pod install fails to find LinkzlySDK. The native iOS SDK is not on CocoaPods Trunk — you must add the explicit pod 'LinkzlySDK', :git => ..., :tag => ... pin shown above to your Podfile.
Universal Links don't open the app. Verify the Associated Domains entitlement is set in Runner.entitlements and Signing & Capabilities, the AASA file is served over HTTPS (Linkzly hosts this for you under Hosted Verification), and the Bundle ID and Team ID match.
App Links don't auto-verify. The assetlinks.json must be served over HTTPS at /.well-known/assetlinks.json (Linkzly hosts this under Hosted Verification) with the correct package name and SHA-256 fingerprint. Re-verify with adb shell pm verify-app-links --re-verify <package>.
Listener never fires. Subscribe to deepLinkStream before calling Linkzly.instance.configure(...) so cold-start data is not missed.
Gradle can't resolve com.github.Linkzly:linkzly-android-sdk. Add JitPack to your Android repositories — either in android/build.gradle.kts (maven("https://jitpack.io")) or in android/settings.gradle.kts under dependencyResolutionManagement.
Was this helpful?
Help us improve our documentation