DropplinkDocsPowered bydropp.

Platforms

Flutter

Add the Flutter plugin and get deep links as a Dart stream.

One Dart API for Android and iOS. The plugin wraps the native Android and iOS SDKs, which do the work. It needs Flutter 3.38 or newer, Android 5.0 (API 21) or newer, and iOS 13 or newer.

  1. Add the package

    Terminal
    flutter pub add dropplink_flutter
    cd ios && pod install
  2. Pass your SDK keys at build time

    Each platform has its own key in Settings → SDK keys. Pass them with --dart-define so they're never in your Dart source:

    Terminal
    flutter run \
      --dart-define=DROPPLINK_ANDROID_KEY_ID=ak_3f9c1e2a7b6d4c80 \
      --dart-define=DROPPLINK_ANDROID_SECRET=your_android_secret \
      --dart-define=DROPPLINK_IOS_KEY_ID=ak_9b2d4f6a8c0e1357 \
      --dart-define=DROPPLINK_IOS_SECRET=your_ios_secret

    To avoid typing them every time, put them in a git-ignored dropplink.json and pass --dart-define-from-file=dropplink.json. In CI, write the file from your secrets.

  3. Initialize in main()

    lib/main.dart
    import 'dart:io';
     
    import 'package:dropplink_flutter/dropplink_flutter.dart';
    import 'package:flutter/foundation.dart';
    import 'package:flutter/material.dart';
     
    Future<void> main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await Dropplink.initialize(
        keyId: Platform.isIOS
            ? const String.fromEnvironment('DROPPLINK_IOS_KEY_ID')
            : const String.fromEnvironment('DROPPLINK_ANDROID_KEY_ID'),
        secret: Platform.isIOS
            ? const String.fromEnvironment('DROPPLINK_IOS_SECRET')
            : const String.fromEnvironment('DROPPLINK_ANDROID_SECRET'),
        debug: kDebugMode,
      );
      runApp(const MyApp());
    }

    initialize returns as soon as the SDK starts. The network call runs in the background.

  4. Listen for deep links

    Subscribe where you can navigate, for example in your root widget's initState:

    lib/app.dart
    late final StreamSubscription<DropplinkDeepLink> _links;
     
    @override
    void initState() {
      super.initState();
      _links = Dropplink.onDeepLink.listen((link) {
        // link.path = "/product/123", link.params = {"variant": "red"}
        router.go(link.path, extra: link.params);
      });
    }
     
    @override
    void dispose() {
      _links.cancel();
      super.dispose();
    }

    If a link arrives before you subscribe, which is common for the deferred link, it's delivered as soon as you do.

  5. Set up App Links and Universal Links

    Finish the deep link setup: the autoVerify intent filter in android/app/src/main/AndroidManifest.xml, and the Associated Domains capability in Xcode. The plugin forwards the links your app is opened with to the SDK by itself, so there's no native code to write.

    Flutter's own deep linking would also try to open the link's code, like /diwali, as a route. Turn it off so links reach your app only through onDeepLink:

    android/app/src/main/AndroidManifest.xml (inside <activity>)
    <meta-data android:name="flutter_deeplinking_enabled" android:value="false" />
    ios/Runner/Info.plist
    <key>FlutterDeepLinkingEnabled</key>
    <false/>

That's the whole integration. Next, test an install before you release.

If your app already receives links through a package like app_links, turn off the plugin's automatic forwarding and pass the links yourself, so each open is reported once:

android/app/src/main/AndroidManifest.xml (inside <application>)
<meta-data android:name="app.ondropp.dropplink.auto_handle_links" android:value="false" />
ios/Runner/Info.plist
<key>DropplinkAutoHandleLinks</key>
<false/>
appLinks.uriLinkStream.listen((uri) => Dropplink.handleDeepLink(uri.toString()));

Events and users#

Anywhere in your app
Dropplink.track('add_to_cart', props: {'sku': 'TSHIRT-M', 'qty': 1});
Dropplink.track('purchase', revenue: Revenue(amount: 499, currency: 'INR', orderId: 'A1023'));
 
await Dropplink.identify(user.id, email: user.email, phone: user.phone); // after sign-up or login
await Dropplink.logout();
 
await Dropplink.setTrackingEnabled(false); // the user declined tracking

Track the funnel with track, and call identify with your own user ID as soon as someone signs up or logs in (and on every launch after that), so their events and revenue follow them. Events are saved on the phone and sent in batches, so you don't need to handle networking or being offline. Send purchases that should pay creators from your server: revenue tracked in the app is shown as app-reported.

More: Tracking events · Users and revenue credit

API#

package:dropplink_flutter/dropplink_flutter.dart
abstract final class Dropplink {
  static Future<void> initialize({required String keyId, required String secret, bool debug = false});
  static Stream<DropplinkDeepLink> get onDeepLink;        // broadcast stream
  static Future<void> handleDeepLink(String url);
  static Future<DropplinkAttribution?> getAttribution();  // null before the first successful launch report
  static Future<String?> getInstallId();
  static Future<void> track(String name, {Map<String, Object?>? props, Revenue? revenue});
  static Future<void> identify(String userId, {String? email, String? phone});
  static Future<void> logout();
  static Future<void> setTrackingEnabled(bool enabled);
  static Future<bool> isTrackingEnabled();
}
 
class Revenue {
  const Revenue({required double amount, required String currency, String? orderId});
}
 
class DropplinkDeepLink {
  final String path;                 // "/product/123"
  final Map<String, String> params;
  final String? url;                 // the opened URL; null for a deferred link
  final bool isDeferred;             // true only for the link delivered after a fresh install
}
 
class DropplinkAttribution {
  final String type;                 // "attributed" or "organic"
  final String method;               // "install_referrer", "fingerprint" or "none"
  final String? clickId, linkId, campaignId, creatorId, attributedAt;
  final String? source;              // the channel, e.g. "instagram"
  final double? confidence;
}

initialize throws a PlatformException with code invalid_config if a key ID or secret is empty or malformed, which usually means a --dart-define is missing. Network errors never reach Dart: the native SDK retries them.

Something unclear? Tell us.Powered bydropp.