Add the dependency to your pubspec.yaml:
dependencies:
dynalink_flutter: ^0.0.13
flutter pub get
Add both intent filters to AndroidManifest.xml:
<!-- Custom scheme (fallback) -->
<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="dynalink-{projectId}" android:host="dynalink.app" />
</intent-filter>
<!-- App Links (verified) -->
<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="{projectPrefix}.dynalink.app" />
</intent-filter>
Add the Asset Links JSON in the Admin Panel under Project → Settings.
Add to ios/Runner/Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>{bundleId}</string>
<key>CFBundleURLSchemes</key>
<array>
<string>dynalink-{projectId}</string>
</array>
</dict>
</array>
<key>NSUserActivityTypes</key>
<array>
<string>NSUserActivityTypeBrowsingWeb</string>
</array>
<key>CFBundleAssociatedDomains</key>
<array>
<string>applinks:{projectPrefix}.dynalink.app</string>
</array>
<key>FlutterDeepLinkingEnabled</key>
<false/>
Add the Apple App Site Association file in the Admin Panel under Project → Settings.
Call initialize before runApp:
import 'package:dynalink_flutter/dynalink_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Dynalink.initialize(
publicKey: 'YOUR_PROJECT_KEY',
projectId: 'YOUR_PROJECT_ID',
);
runApp(const MyApp());
}
Listen to dynamicLinkStream to receive every processed link:
Dynalink.instance.dynamicLinkStream.listen((DynalinkEvent event) {
// Navigate to the resolved destination
navigateTo(event.actualUrl);
// Campaign / UTM context
if (event.hasCampaign) {
analytics.setCampaign(event.campaignId, event.utmCampaign);
}
// Forward click IDs to your ad-network measurement SDKs
if (event.gclid != null) googleAds.reportConversion(event.gclid!);
if (event.fbclid != null) facebookCapi.reportInstall(event.fbclid!, event.fbclidCapturedAt);
if (event.ttclid != null) tiktokEvents.reportInstall(event.ttclid!);
});
| Field | Type | Description |
|---|---|---|
actualUrl |
String |
Resolved destination URL |
campaignId |
int? |
Campaign ID |
campaignName |
String? |
Campaign display name |
utmSource |
String? |
UTM source |
utmMedium |
String? |
UTM medium |
utmCampaign |
String? |
UTM campaign slug |
utmTerm |
String? |
UTM term |
utmContent |
String? |
UTM content |
gclid |
String? |
Google Ads click ID |
gbraid |
String? |
Google Ads enhanced click ID (iOS 14.5+) |
fbclid |
String? |
Meta (Facebook/Instagram) click ID |
fbclidCapturedAt |
DateTime? |
When fbclid was first captured in the browser |
ttclid |
String? |
TikTok click ID |
twclid |
String? |
X (Twitter) click ID |
liFatId |
String? |
LinkedIn first-party ad tracking ID |
attributedAt |
DateTime? |
When the backend confirmed the install match |
hasCampaign |
bool |
Convenience getter — true when campaignId != null |
hasClickIds |
bool |
Convenience getter — true when any click ID is non-null |
Click IDs are written to SharedPreferences automatically. Use these getters if you need the values outside of the stream:
final gclid = await Dynalink.instance.getLastGclid();
final gbraid = await Dynalink.instance.getLastGbraid();
final fbclid = await Dynalink.instance.getLastFbclid();
final ttclid = await Dynalink.instance.getLastTtclid();
final twclid = await Dynalink.instance.getLastTwclid();
final liFatId = await Dynalink.instance.getLastLiFatId();
final campaignId = await Dynalink.instance.getLastCampaignId();
Query attribution data on demand:
// By device fingerprint
final result = await Dynalink.instance.getAttributionByFingerprint(fingerprint);
// By DynaLink short code
final result = await Dynalink.instance.getAttributionByCode('SUMR99');
if (result != null && result.matched) {
print('gclid: ${result.gclid}');
print('fbclid: ${result.fbclid}');
print('attributed at: ${result.attributedAt}');
}
final url = await Dynalink.instance.createShortenedLink(
CreateDynalinkForm(
url: 'https://yourapp.io?screen=offer&id=99',
isDeepLink: true,
iosUrl: 'https://apps.apple.com/app/id123456789',
androidUrl: 'https://play.google.com/store/apps/details?id=com.yourapp',
fallbackUrl: 'https://yourapp.io',
campaignId: 4,
),
);
// List campaigns
final campaigns = await Dynalink.instance.getCampaigns(status: 'active');
// Get stats (last N days)
final stats = await Dynalink.instance.getCampaignStats(4, days: 7);
print('Total clicks: ${stats?.totalClicks}');
for (final day in stats?.clicksPerDay ?? []) {
print('${day.date}: ${day.count}');
}
// Create
final campaign = await Dynalink.instance.createCampaign(
CreateCampaignForm(name: 'Summer 2026', utmSource: 'google', utmMedium: 'cpc'),
);
// Update
await Dynalink.instance.updateCampaign(campaign.id, CreateCampaignForm(name: 'Summer 2026 — Revised'));
// Delete
await Dynalink.instance.deleteCampaign(campaign.id);
DynalinkEvent is emitted on dynamicLinkStream with the full payload. Values are also saved to SharedPreferences.