Skip to main content

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.


Follow the same prerequisites from the overview guide:

  • Configuration → Android/iOS: package IDs, URL schemes, store fallbacks.
  • Domains: verify the shared .shared.ly domain or a custom host.
  • Links: recreate Firebase slugs with the right type (dynamic vs unified) 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());
}
  • ULinkConfig mirrors 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.

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;
}
  • onDynamicLink surfaces the “deep” experience (equivalent to Firebase dynamic links).
  • onUnifiedLink surfaces 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 a GET /sdk/resolve.

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

  1. Use adb / xcrun commands from Troubleshoot → Testing Deep Links with the new URLs.
  2. Review the ULink logs emitted when debug: true is set—both Android and iOS native logs bubble up through Flutter.
  3. 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.