Flutter SDK
The Flutter Redirectly SDK is a pure Dart implementation that enables dynamic links and app install tracking in your Flutter applications. No native code required!
Installation
Add flutter_redirectly to your pubspec.yaml:
dependencies:
flutter_redirectly: ^2.1.6Then run:
flutter pub getQuick Setup
1. Get Your API Key
Sign up at redirectly.app to get your free API key and create your subdomain.
2. Configure Deep Links
Android Configuration
Add the following intent filter to android/app/src/main/AndroidManifest.xml:
<activity
android:name=".MainActivity"
android:exported="true"
...>
<!-- Existing intent filters -->
<!-- Add Redirectly deep link intent filter -->
<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_SUBDOMAIN.redirectly.app" />
</intent-filter>
</activity>Replace YOUR_SUBDOMAIN with your actual subdomain (e.g., myapp.redirectly.app).
iOS Configuration
Add Associated Domains to 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_SUBDOMAIN.redirectly.app</string>
</array>
</dict>
</plist>Important iOS Notes:
- If
Runner.entitlementsdoesn’t exist, create it - Ensure your app is properly signed
- Enable Associated Domains capability in your Apple Developer account
- Add the domain to your App ID in Apple Developer Portal
3. Initialize the SDK
Initialize Redirectly in your app (typically in main.dart or your app’s initialization):
import 'package:flutter_redirectly/flutter_redirectly.dart';
final redirectly = FlutterRedirectly();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await redirectly.initialize(RedirectlyConfig(
apiKey: 'your-api-key-here',
debug: true, // Set to false in production
));
runApp(MyApp());
}4. Handle Incoming Links
Listen for incoming links when your app is running:
// Listen for incoming links
redirectly.onLinkClick.listen((linkClick) {
print('Link clicked: ${linkClick.originalUrl}');
print('Slug: ${linkClick.slug}');
print('Username: ${linkClick.username}');
// Navigate to the target content
if (linkClick.linkResolution != null) {
final target = linkClick.linkResolution!.target;
// Handle navigation based on target URL
_navigateToTarget(target);
}
});Handle links when app is launched from a link:
// Check for initial link on app launch
final initialLink = await redirectly.getInitialLink();
if (initialLink != null) {
// App was opened via a Redirectly link
print('App opened from link: ${initialLink.slug}');
_handleLinkClick(initialLink);
}5. Track App Installs
The SDK automatically tracks app installs on first launch. Listen for install attribution:
// Listen for app install events
redirectly.onAppInstalled.listen((installEvent) {
if (installEvent.matched) {
// User installed app after clicking a Redirectly link
print('Install attributed to: ${installEvent.username}/${installEvent.slug}');
print('Original click time: ${installEvent.clickedAt}');
// Show personalized onboarding or deep link to specific content
_handleAttributedInstall(installEvent);
} else {
// Organic install (no prior link click)
print('Organic app install');
_handleOrganicInstall();
}
});What gets tracked:
- First app launch only (subsequent launches are ignored)
- Device information (OS, version, timezone, language)
- App information (version, build number)
- Attribution matching with prior link clicks (if any)
Privacy-friendly:
- No personal information is collected
- Only standard app/device metadata
- Tracking file prevents duplicate logging
- All data stays within your Redirectly account
6. Create Links Programmatically
Create permanent links:
// Create a permanent link
final link = await redirectly.createLink(
slug: 'my-link',
target: 'https://example.com/page',
);
print('Created link: ${link.url}');
print('Link ID: ${link.id}');Create temporary links (expires after specified time):
// Create a temporary link (expires in 1 hour)
final tempLink = await redirectly.createTempLink(
target: 'https://example.com/page',
ttlSeconds: 3600, // 1 hour
);
print('Temp link: ${tempLink.url}');
print('Expires at: ${tempLink.expiresAt}');Complete Example
Here’s a complete example showing the full attribution flow:
import 'package:flutter/material.dart';
import 'package:flutter_redirectly/flutter_redirectly.dart';
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final redirectly = FlutterRedirectly();
@override
void initState() {
super.initState();
_initializeRedirectly();
}
Future<void> _initializeRedirectly() async {
await redirectly.initialize(RedirectlyConfig(
apiKey: 'your-api-key',
debug: true,
));
// Handle ongoing link clicks
redirectly.onLinkClick.listen(_handleLinkClick);
// Handle app install attribution
redirectly.onAppInstalled.listen(_handleAppInstall);
// Handle initial link if app was opened via link
final initialLink = await redirectly.getInitialLink();
if (initialLink != null) {
_handleLinkClick(initialLink);
}
}
void _handleLinkClick(RedirectlyLinkClick linkClick) {
print('Link clicked: ${linkClick.slug}');
if (linkClick.linkResolution != null) {
// Navigate based on link target
_navigateToTarget(linkClick.linkResolution!.target);
}
}
void _handleAppInstall(RedirectlyAppInstallResponse install) {
if (install.matched) {
// Show attributed install welcome
_showAttributedWelcome(install);
} else {
// Show standard onboarding
_showStandardOnboarding();
}
}
void _showAttributedWelcome(RedirectlyAppInstallResponse install) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Welcome!'),
content: Text('Thanks for installing from our ${install.slug} link!'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
// Navigate to the content they were originally trying to reach
if (install.linkResolution != null) {
_navigateToTarget(install.linkResolution!.target);
}
},
child: Text('Get Started'),
),
],
),
);
}
void _navigateToTarget(String target) {
// Handle navigation based on your app's routing
print('Navigating to: $target');
}
void _showStandardOnboarding() {
// Show your standard onboarding flow
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: Scaffold(
appBar: AppBar(title: Text('My App')),
body: Center(child: Text('Welcome')),
),
);
}
}Error Handling
Handle errors gracefully:
try {
final link = await redirectly.createLink(
slug: 'test',
target: 'https://example.com',
);
} on RedirectlyError catch (e) {
print('Error: ${e.message}');
// Handle error (e.g., show user-friendly message)
}API Reference
Configuration
RedirectlyConfig({
required String apiKey, // Your API key
String? baseUrl, // Optional: custom domain
bool enableDebugLogging, // Optional: debug mode
})Models
RedirectlyLink (Permanent Link)
RedirectlyLink({
String id, // Unique link ID
String slug, // Link slug/identifier
String target, // Target URL
String url, // Full Redirectly URL
int clickCount, // Number of clicks
DateTime createdAt, // Creation timestamp
Map<String, dynamic>? metadata, // Custom metadata
})RedirectlyTempLink (Temporary Link)
RedirectlyTempLink({
String id, // Unique link ID
String slug, // Auto-generated slug
String target, // Target URL
String url, // Full Redirectly URL
int ttlSeconds, // Time to live in seconds
DateTime expiresAt, // Expiration timestamp
DateTime createdAt, // Creation timestamp
})RedirectlyLinkClick (Link Click Event)
RedirectlyLinkClick({
String originalUrl, // Original clicked URL
String slug, // Link slug
String username, // Link owner username
DateTime receivedAt, // When click was received
RedirectlyLinkResolution? linkResolution, // Link details
RedirectlyError? error, // Error if any
})RedirectlyAppInstallResponse (Install Attribution)
RedirectlyAppInstallResponse({
bool matched, // Whether install was attributed
String? username, // Attribution username (if matched)
String? slug, // Attribution slug (if matched)
DateTime? clickedAt, // Original click time (if matched)
RedirectlyLinkResolution? linkResolution, // Original link details
DateTime loggedAt, // When install was logged
})Streams
// Listen for link clicks (app running or launch)
Stream<RedirectlyLinkClick> redirectly.onLinkClick
// Listen for app install events (first launch only)
Stream<RedirectlyAppInstallResponse> redirectly.onAppInstalledTesting Install Attribution
During development, test install attribution by:
- Delete and reinstall your app to simulate first install
- Clear app data on Android or delete from device on iOS
- Click a Redirectly link → install app → open app
- The
onAppInstalledevent should fire withmatched: true
The plugin automatically prevents duplicate install logging by creating a tracking file on first launch.
Best Practices
- Initialize Early - Initialize Redirectly as early as possible in your app lifecycle
- Handle Errors - Always wrap API calls in try-catch blocks
- Test Deep Links - Test both app-installed and app-not-installed scenarios
- Privacy - Inform users about link tracking in your privacy policy
- Debug Mode - Use
debug: trueduring development,falsein production
Support
Package Info
- Package: flutter_redirectly on pub.dev
- Version: ^2.1.6
- License: MIT