smler_deferred_link 1.2.0 copy "smler_deferred_link: ^1.2.0" to clipboard
smler_deferred_link: ^1.2.0 copied to clipboard

Lightweight Flutter plugin enabling Android Install Referrer & iOS clipboard deferred deep linking to land users on exact screens.

Flutter Deferred Deep Link #

Pub Version Pub Likes Pub Points Popularity

A powerful yet lightweight Flutter plugin for deferred deep linking built for real production apps. It helps you extract referral information and deep link parameters on both Android and iOS without heavy attribution SDKs.

πŸ“Œ What Is Deferred Deep Linking? #

Deferred deep linking allows your user to install your app after clicking a link, and still land on the correct screen or carry referral metadata after install.

πŸ“˜ How It Works β€” Deferred Deep Linking (Android + iOS) #

If the user has not installed the app and they click a deep link, it will first open in the phone’s default browser. From the browser, the system automatically detects the platform (Android or iOS) and redirects the user to the respective store:

Android β†’ Google Play Store

iOS β†’ Apple App Store

After installation and first app launch, the app will be able to read the deferred deep-link parameters and navigate to the exact intended screen inside the app.

This is the core idea of Deferred Deep Linking β€” opening the correct screen after the app is installed.

If you require direct deep linking (when the app is already installed), you should use packages like app_links or uni_links. This plugin focuses specifically on Deferred Deep Linking, not direct runtime linking.

You do not need Branch, Adjust, AppsFlyer, or any other paid SDK. Everything works using native platform features.

Platform Behavior #

Android

We use the Google Play Install Referrer API, which is officially supported by Google. This API lets us read details from:

https://play.google.com/store/apps/details?id=<package>&referrer=<encoded_params>

From the referrer parameter, we decode and route the user to the correct screen.

iOS

Deferred deep linking usually works out-of-the-box for many iOS users. However, for users with iCloud+ Private Relay enabled, their IP address is masked, preventing proper session matching by servers.

To avoid this problem, we use an alternative solution:

βœ” The deep link is copied to the clipboard

βœ” When the app is opened the first time, we read the clipboard

βœ” If the link matches your allowed domains, we extract parameters and navigate to the correct screen

This ensures deferred linking works reliably, even under Private Relay.

Backend Support (Important) #

You must handle one small backend/website step:
When a user clicks the deep link, the web page should redirect them to:

Android

https://play.google.com/store/apps/details?id=<your.package>&referrer=<param>%3D<value>

Encode your parameters properly
The app will decode

iOS

Your webpage should ensure the deep link is placed in the clipboard:

example.com?referrer=<value>&page=<screen>

The plugin will read the clipboard to retrieve these values on first app launch.

This plugin solves both platforms:

Platform How It Works
Android Uses official Google Play Install Referrer API to read the referrer param from Play Store.
iOS Reads clipboard deep links (URL copied before launching app). Pattern-matches domains, subdomains, and paths, then extracts query parameters.
Advanced Optional probabilistic matching for enhanced attribution when traditional methods fail (requires network call).

πŸš€ Why Use This Plugin? #

βœ” Lightweight (no SDKs like Branch / Adjust / AppsFlyer)

βœ” Core features work 100% offline

βœ” Optional probabilistic matching for advanced attribution

βœ” Automatic iOS fallback when clipboard is empty

βœ” Zero configuration on backend for basic usage

βœ” Works from 1st launch

βœ” Supports unlimited custom query params

βœ” Works with any URL structure

βœ” Subdomains + www + scheme normalization

βœ” Clean, safe architecture with cached responses

🧠 Use Cases #

βœ… Track marketing campaign using:

?referrer=campaign123

βœ… Store affiliate codes

βœ… Open after-install screens:

https://example.com/profile?uid=1001

βœ… Route iOS users from Safari β†’ clipboard β†’ app

βœ… Internal routing: /bonus?referrer=promo50

βœ… Attribution without Firebase Dynamic Links / Branch

βœ… Fallback attribution when clipboard is empty (iOS)

βœ… Cross-platform probabilistic device fingerprinting

βœ… High-confidence install-to-click matching via API

πŸ— Architecture Overview #

Flutter App
|
|-- Platform.isAndroid ---------------------------|
|                                                 |
|    Android Native (Kotlin)                      |
|    - InstallReferrerClient                      |
|    - Single connection + retry                  |
|    - Cache result                               |
|    - Return Map to Dart -----> ReferrerInfo     |
|                                                 |
|-- Platform.isIOS --------------------------------|
|                                             |
iOS Clipboard Reader (Dart)                   |
- Reads Clipboard.kTextPlain                  |
- Pattern matcher (domain/path/subdomain)     |
- Parses as URI ----------------> IosClipboardDeepLinkResult

πŸ“¦ Installation #

Add:

dependencies:
  smler_deferred_link: <latest-version>

The plugin automatically includes:

  • http for API calls (probabilistic matching)
  • device_info_plus for device fingerprinting

βš™ Android Setup #

The plugin already includes:

implementation "com.android.installreferrer:installreferrer:2.2"

No permissions are required.

🍏 iOS Setup #

Nothing special needed.

The plugin uses:

Clipboard.getData(Clipboard.kTextPlain)

This works on all iOS versions supported by Flutter.

πŸ” Permissions No permissions required on both platforms.

πŸ“š API Reference #

πŸ“Œ 1. Android: getInstallReferrerAndroid()

Reads Google Play Install Referrer once.

final info = await SmlerkDeferredLink.getInstallReferrerAndroid();

Returns: ReferrerInfo

info.installReferrer; // raw "utm_source=...&referrer=..."
info.asQueryParameters; // parsed params Map<String, String>
info.referrerClickTimestampSeconds;
info.installBeginTimestampSeconds;
info.installVersion;info.googlePlayInstantParam;

Example

final info = await SmlerDeferredLink.getInstallReferrerAndroid();
final params = info.asQueryParameters;

debugPrint(params['referrer']); // campaign123
debugPrint(params['uid']); // optional

Extracting shortCode and dltHeader from Path #

ReferrerInfo.extractShortCodeAndDltHeader() extracts structured path segments from the referrer URL.

Supports two formats:

  • https://domain.com/[dltHeader]/[shortCode] - with optional dltHeader
  • https://domain.com/[shortCode] - shortCode only

Example:

final info = await SmlerDeferredLink.getInstallReferrerAndroid();
final pathParams = info.extractShortCodeAndDltHeader();

debugPrint(pathParams['shortCode']); // e.g., "abc123"
debugPrint(pathParams['dltHeader']); // e.g., "promo" or null

Tracking Clicks #

ReferrerInfo.trackClick() automatically sends tracking data to the Smler API.

This method:

  • Extracts clickId from the clickId query parameter
  • Extracts shortCode and optional dltHeader from the URL path
  • Sends tracking data to https://smler.in/api/v2/track/{clickId}
  • Returns null if clickId doesn't exist (no API call made)

Example:

final info = await SmlerDeferredLink.getInstallReferrerAndroid();
final response = await info.trackClick();

if (response != null) {
  debugPrint('Tracking successful: $response');
} else {
  debugPrint('No clickId found, tracking skipped');
}

Extracting a Single Query Parameter #

ReferrerInfo.getParam(key) lets you safely extract a single query parameter from the install referrer string β€” regardless of how Google Play sends it.

This method works with:

βœ” Example (Android Install Referrer)

final info = await SmlerDeferredLink.getInstallReferrerAndroid();
final ref = info.getParam('ref');
print(ref); // e.g. "promo123"

βœ” Example (Multiple params)

final campaign = info.getParam('utm_campaign');
final source = info.getParam('utm_source');

Throws

Exception Reason
UnsupportedError Called on iOS/Web/Desktop
PlatformException Play service unavailable, feature not supported
StateError Unexpected parsing issues

πŸ“Œ 2. iOS: getInstallReferrerIos() Reads clipboard β†’ checks patterns β†’ returns matched deep link + params.

final result = await SmlerDeferredLink.getInstallReferrerIos(deepLinks: ["https://example.com/profile","example.com","sub.example.com"]);

Returns: IosClipboardDeepLinkResult?

result.fullReferralDeepLinkPath; // full string
result.queryParameters; // parsed params
result.getParam("referrer"); // campaign123
result.getParam("uid");

Extracting shortCode and dltHeader from Path (iOS) #

IosClipboardDeepLinkResult.extractShortCodeAndDltHeader() extracts structured path segments from the deep link URL.

Supports two formats:

  • https://domain.com/[dltHeader]/[shortCode] - with optional dltHeader
  • https://domain.com/[shortCode] - shortCode only

Example:

final result = await SmlerDeferredLink.getInstallReferrerIos(
  deepLinks: ["example.com"],
);

if (result != null) {
  final pathParams = result.extractShortCodeAndDltHeader();
  debugPrint(pathParams['shortCode']); // e.g., "abc123"
  debugPrint(pathParams['dltHeader']); // e.g., "promo" or null
}

Tracking Clicks (iOS) #

IosClipboardDeepLinkResult.trackClick() automatically sends tracking data to the Smler API.

This method:

  • Extracts clickId from the clickId query parameter
  • Extracts shortCode and optional dltHeader from the URL path
  • Sends tracking data to https://smler.in/api/v2/track/{clickId}
  • Returns null if clickId doesn't exist (no API call made)

Example:

final result = await SmlerDeferredLink.getInstallReferrerIos(
  deepLinks: ["example.com"],
);

if (result != null) {
  final response = await result.trackClick();
  
  if (response != null) {
    debugPrint('Tracking successful: $response');
  } else {
    debugPrint('No clickId found, tracking skipped');
  }
}

Matching Rules

Accepts:

http://, https://, or no scheme

Subdomains (m.example.com, sub.example.com)

www. variants

Path must match pattern prefix (optional)

Example

final res = await SmlerDeferredLink.getInstallReferrerIos(deepLinks: ["example.com", "example.com/profile"]);

if (res != null) {
  final referrer = res.getParam('referrer');
  debugPrint("iOS Referrer: $referrer");
}

πŸ“Š Probabilistic Matching (Advanced Attribution) #

Probabilistic matching enables accurate install attribution by analyzing device and network fingerprints when traditional methods fail or return insufficient data. This is particularly useful for iOS users when clipboard matching fails or for validating Android install referrer data.

When to Use Probabilistic Matching #

βœ… iOS Fallback: When getInstallReferrerIos() returns null (clipboard empty or no match)

βœ… Cross-Platform Validation: Confirm install attribution across both platforms

βœ… Enhanced Attribution: Get additional click metadata and campaign information

How It Works #

  1. Automatic Device Detection: Plugin automatically extracts device model, OS version, and platform
  2. API Matching: Sends fingerprint to Smler API for probabilistic matching
  3. Confidence Score: Returns a match score (0.0 to 1.0) indicating confidence level
  4. Smart Threshold: Only fetch tracking data when score > 0.65 for high-confidence matches

πŸ“Œ API: getProbabilisticMatch() #

Performs probabilistic matching to link install events to clicks.

final result = await HelperReferrer.getProbabilisticMatch(
  domain: 'example.com',
  clickId: 'optional-click-id', // optional
);

Parameters:

  • domain (required): Your domain name (e.g., "example.com")
  • clickId (optional): Click identifier for enhanced matching

Returns: Map<String, dynamic> with:

  • matched (bool): Whether a match was found
  • score (double): Confidence score (0.0 - 1.0)
  • matchedAttributes (List): Attributes that matched
  • clickDetails (Map): Details of the matched click
  • shortUrl (Map): Complete short URL object with metadata
  • fingerprint (Map): Device fingerprint data
  • domain (String): Extracted domain from shortUrl
  • pathParams (Map): Contains shortCode, dltHeader, and domain

Example:

import 'package:smler_deferred_link/src/helpers.dart';

final result = await HelperReferrer.getProbabilisticMatch(
  domain: 'example.com',
);

if (result['matched'] == true) {
  final score = result['score'] as double;
  print('Match confidence: $score');
  
  if (score > 0.65) {
    // High confidence - proceed with attribution
    final pathParams = result['pathParams'];
    print('Short code: ${pathParams['shortCode']}');
    print('Domain: ${pathParams['domain']}');
  }
}

πŸ“Œ API: fetchTrackingData() #

Fetches detailed tracking data from the Smler API when you have a clickId.

final trackingData = await HelperReferrer.fetchTrackingData(
  clickId,
  pathParams,
  domain,
);

Parameters:

  • clickId (String): The click ID from query parameters
  • pathParams (Map<String, String?>): Map containing shortCode and optional dltHeader
  • domain (String?): The domain name from the referrer URL

Returns: Map<String, dynamic> with API response data or error information

Example:

final clickDetails = result['clickDetails'] as Map<String, dynamic>?;
final clickId = clickDetails?['clickId'] as String?;
final pathParams = result['pathParams'] as Map<String, dynamic>?;

if (clickId != null && pathParams != null) {
  final trackingData = await HelperReferrer.fetchTrackingData(
    clickId,
    Map<String, String?>.from(pathParams),
    result['domain'] as String?,
  );
  
  print('Tracking data: $trackingData');
}

Use probabilistic matching as a fallback when clipboard matching fails on iOS:

Future<void> _loadInstallReferrerIos() async {
  try {
    final result = await SmlerDeferredLink.getInstallReferrerIos(
      deepLinks: ['example.com', 'example.com/profile'],
    );

    if (result == null) {
      // Clipboard empty or no match - fall back to probabilistic
      debugPrint('πŸ“Š Falling back to probabilistic matching...');
      await _tryProbabilisticMatch('example.com');
      return;
    }

    // Process clipboard result
    final params = result.queryParameters;
    // ... handle navigation
  } catch (e) {
    debugPrint('Error: $e');
  }
}

Future<void> _tryProbabilisticMatch(String domain) async {
  final result = await HelperReferrer.getProbabilisticMatch(
    domain: domain,
  );

  if (result['matched'] == true) {
    final score = result['score'] as double;
    
    if (score > 0.65) {
      // High confidence - fetch tracking data
      final clickDetails = result['clickDetails'] as Map?;
      final clickId = clickDetails?['clickId'] as String?;
      final pathParams = result['pathParams'] as Map?;

      if (clickId != null && pathParams != null) {
        final trackingData = await HelperReferrer.fetchTrackingData(
          clickId,
          Map<String, String?>.from(pathParams),
          result['domain'] as String?,
        );
        
        // Process tracking data and navigate
        debugPrint('Attribution confirmed: $trackingData');
      }
    }
  }
}

🎯 Android Enhanced Attribution #

Combine install referrer with probabilistic matching for complete attribution:

Future<void> _loadInstallReferrerAndroid() async {
  final info = await SmlerDeferredLink.getInstallReferrerAndroid();
  final params = info.asQueryParameters;
  
  // Get basic referrer data
  debugPrint('Referrer: ${info.installReferrer}');
  
  // Enhance with probabilistic matching
  final result = await HelperReferrer.getProbabilisticMatch(
    domain: 'example.com',
  );
  
  if (result['matched'] == true && result['score'] > 0.65) {
    // Cross-validate attribution
    debugPrint('Probabilistic match confirms attribution');
    // ... proceed with tracking
  }
}

⚑ No Permissions Required #

Device and OS information is automatically extracted using the device_info_plus package without requiring any special permissions. The plugin accesses:

βœ… Device manufacturer and model (e.g., "Samsung Galaxy S21")

βœ… OS version (e.g., "Android 13", "iOS 16.4")

βœ… System name and basic hardware info

❌ No unique identifiers (IMEI, serial numbers, advertising IDs)

❌ No runtime permission dialogs

❌ No manifest/plist configuration needed

πŸ§ͺ Full Usage Example (Android + iOS) #

void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String? status;
  Map<String, String> params = {};
  Map<String, dynamic>? trackingResponse;

  @override
  void initState() {
    super.initState();
    _init();
  }

  Future<void> _init() async {
    try {
      if (Platform.isAndroid) {
        final info = await SmlerDeferredLink.getInstallReferrerAndroid();
        params = info.asQueryParameters;
        status = "Android Referrer Loaded";
        
        // Track click automatically
        trackingResponse = await info.trackClick();
      } else if (Platform.isIOS) {
        final res = await SmlerDeferredLink.getInstallReferrerIos(
          deepLinks: ["example.com", "example.com/profile"],
        );
        if (res != null) {
          params = res.queryParameters;
          status = "iOS Clipboard Deep Link Loaded";
          
          // Track click automatically
          trackingResponse = await res.trackClick();
        } else {
          status = "No deep link found";
        }
      }
    } catch (e) {
      status = "Error: $e";
    }

    setState(() {});
  }

  @override
  Widget build(BuildContext ctx) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text("Smler Deferred Link")),
        body: Padding(
          padding: const EdgeInsets.all(16),
          child: ListView(
            children: [
              Text(status ?? "Loading..."),
              const SizedBox(height: 20),
              const Text("Params:", style: TextStyle(fontSize: 18)),
              ...params.entries.map((e) => Text("${e.key}: ${e.value}")),
              if (trackingResponse != null) ...[
                const SizedBox(height: 20),
                const Text("Tracking Response:", style: TextStyle(fontSize: 18)),
                Text(trackingResponse.toString()),
              ],
            ],
          ),
        ),
      ),
    );
  }
}

🧠 Best Practices #

βœ” Call API only once on first screen

The plugin caches results automatically.

βœ” Store result locally

Install referrer is static and won’t change.

βœ” For iOS

Use clipboard reading only on first launch, optional:

await Clipboard.setData(const ClipboardData(text: ""));

πŸ” Troubleshooting #

❓ Android returns empty referrer

Play Store did not include any referrer parameter. Consider using probabilistic matching as a fallback.

❓ iOS returns null

Clipboard may be empty or the link does not match any allowed pattern. Use probabilistic matching as a fallback strategy.

❓ iOS parsing fails

Ensure your passed URL patterns include base domains.

❓ Cannot parse URL

Clipboard might contain text that is not a URL.

❓ Probabilistic match score is too low (< 0.65)

This indicates low confidence in the match. The device fingerprint may not match any recent clicks, or multiple similar clicks exist. Only proceed with attribution if you accept lower confidence.

❓ Probabilistic matching returns an error

Check your network connection and ensure the domain parameter is correct. The API endpoint must be reachable.

Clipboard might contain text that is not a URL.

❓ FAQ #

Does this plugin track users?

Core features (Install Referrer API & clipboard reading) are 100% offline with no network calls. Optional probabilistic matching and tracking APIs make network calls to the Smler API only when explicitly invoked.

Can I use this without making network calls?

Yes. Simply don't call getProbabilisticMatch() or fetchTrackingData(). The basic Install Referrer and clipboard features work completely offline.

Can I clear Android referrer?

No. Google Play controls it. You can ignore it after reading.

Is clipboard reading safe / allowed?

Yes, Flutter allows access to clipboard text.

Can it handle /path/subpath?

Yes. Pattern paths must match prefix.

For more information see https://developer.android.com/google/play/installreferrer

0
likes
140
points
104
downloads

Publisher

verified publisherapp.smler.io

Weekly Downloads

Lightweight Flutter plugin enabling Android Install Referrer & iOS clipboard deferred deep linking to land users on exact screens.

Repository (GitHub)
View/report issues

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

device_info_plus, flutter, http, plugin_platform_interface

More

Packages that depend on smler_deferred_link

Packages that implement smler_deferred_link