Migrate Firebase Dynamic Links in Flutter
The flutter_ulink_sdk package bridges the native Android/iOS SDKs so you can retire Firebase Dynamic Links while keeping a single Dart interface. Use this guide to swap dependencies and update your Flutter app.
1. Install the package
Update pubspec.yaml as described in sdk/flutter_ulink_sdk/README.md:
dependencies:
flutter_ulink_sdk: ^1.0.0
Run flutter pub get to pull the package.
2. Configure your ULink project
Follow the same prerequisites from the overview guide:
- Configuration → Android/iOS: package IDs, URL schemes, store fallbacks.
- Domains: verify the shared
.shared.lydomain or a custom host. - Links: recreate Firebase slugs with the right type (
dynamicvsunified) or import via/sdk/links.
Once those steps are done, the rest of this page focuses on Flutter-specific code changes.
3. Initialize the SDK
Replace FirebaseDynamicLinks.instance.onLink.listen logic with the ULink initialization in main.dart:
import 'package:flutter/material.dart';
import 'package:flutter_ulink_sdk/flutter_ulink_sdk.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final config = ULinkConfig(
apiKey: 'ULINK_API_KEY_FROM_DASHBOARD',
baseUrl: 'https://api.ulink.ly',
debug: true,
);
await ULink.instance.initialize(config);
runApp(const MyApp());
}
ULinkConfigmirrors the native config objects (API key, base URL, debug flag).- The plugin automatically bridges to the underlying Android/iOS SDKs included in your Flutter app.
4. Handle incoming links
Listen to the provided streams (see flutter_ulink_sdk/lib/flutter_ulink_sdk.dart):
class DeepLinkObserver extends StatefulWidget {
const DeepLinkObserver({super.key, required this.child});
final Widget child;
State<DeepLinkObserver> createState() => _DeepLinkObserverState();
}
class _DeepLinkObserverState extends State<DeepLinkObserver> {
StreamSubscription<ULinkResolvedData>? _dynamicSub;
StreamSubscription<ULinkResolvedData>? _unifiedSub;
void initState() {
super.initState();
_dynamicSub = ULink.instance.onDynamicLink.listen((resolved) {
handleDynamicLink(resolved);
});
_unifiedSub = ULink.instance.onUnifiedLink.listen((resolved) {
handleUnifiedLink(resolved);
});
}
void dispose() {
_dynamicSub?.cancel();
_unifiedSub?.cancel();
super.dispose();
}
Widget build(BuildContext context) => widget.child;
}
onDynamicLinksurfaces the “deep” experience (equivalent to Firebase dynamic links).onUnifiedLinksurfaces platform-aware marketing links so you can decide whether to open a browser or stay inside the app.- If you need to interrogate an incoming URI manually, call
ULink.instance.processULinkUri(Uri.parse(url))which performs aGET /sdk/resolve.
5. Replace Firebase link creation
Where you previously called Firebase REST helpers, switch to the SDK’s createLink method. It wraps the backend /sdk/links endpoint:
final response = await ULink.instance.createLink(
ULinkParameters.dynamic(
slug: 'flutter-launch',
domain: 'links.shared.ly',
iosFallbackUrl: 'https://apps.apple.com/app/id123456789',
androidFallbackUrl: 'https://play.google.com/store/apps/details?id=com.example.app',
fallbackUrl: 'https://example.com/launch',
parameters: {
'utm_source': 'flutter-app',
'screen': 'offer'
},
),
);
if (response.success) {
debugPrint('Created link: ${response.url}');
}
Unified links use ULinkParameters.unified, which takes separate platform URLs instead of fallbacks.
6. Validate end-to-end
- Use
adb/xcruncommands from Troubleshoot → Testing Deep Links with the new URLs. - Review the ULink logs emitted when
debug: trueis set—both Android and iOS native logs bubble up through Flutter. - Watch Dashboard → Links → Analytics for new clicks and per-device stats to ensure telemetry matches the Firebase benchmarks you previously tracked.
After these steps, remove the Firebase Dynamic Links package from pubspec.yaml. Your Flutter app will be fully backed by the ULink SDKs and APIs.