Skip to main content

wherefrom 0.4.0 copy "wherefrom: ^0.4.0" to clipboard
wherefrom: ^0.4.0 copied to clipboard

Flutter plugin: Play Install Referrer (Android) and Apple AdServices (iOS) for install attribution and deferred deep links, with typed results and first-launch-only reads.

wherefrom #

A Flutter plugin that tells your app where an install came from (the Google Play Install Referrer on Android, the Apple AdServices token on iOS) and turns it into the deep link the user wanted before they installed.

pub version pub points pub likes CI license: MIT

Why #

  • Firebase Dynamic Links is gone. To carry a link through a store install, you now read the platform's own referrer yourself.
  • Reading it safely is full of traps: a Binder call that freezes the UI, a null bundle that throws, a value decoded twice, and a 90-day-old referrer routed again on every launch or after an update.
  • You want one typed answer on both platforms, without manifest edits, Info.plist keys or an App Tracking Transparency prompt.

Install #

flutter pub add wherefrom

Flutter 3.44+ / Dart 3.12+, Android minSdk 21, iOS 15. Swift Package Manager and CocoaPods both work. Nothing to configure on Android or iOS, no R8 keep rules, no native code (16 KB page ready); CI builds release versions for both stores (setup details).

Coming from imaji_install_attribution #

This package was imaji_install_attribution up to 0.2.2. Replace the dependency with wherefrom: ^0.4.0 and the import with package:wherefrom/wherefrom.dart; fakes in tests extend WherefromPlatform. An upgraded app keeps its consume-once state. See the CHANGELOG.

Quick start #

import 'package:wherefrom/wherefrom.dart';

Future<void> routeFromInstall(GoRouter router) async {
  // null once handled, while another startup path holds the claim, or after
  // a failed read (retried on the next launch, up to a budget).
  final claim = await InstallAttribution.claimOnce();
  if (claim == null) return;
  switch (claim.result) {
    case Attributed(:final referrer?):
      final link = referrer.deepLink(allow: ['/items/', '/profile/']);
      if (link != null) router.go(link.toString());
    case Attributed(:final token?):
      await sendToServer(token); // iOS: resolve with Apple's attribution API
    case _:
      break; // installed with no campaign
  }
  await claim.complete(); // never again on this install
}

Failures are typed, never thrown: InstallAttribution.lastSkipReason and lastFailure say why claimOnce() returned null. To try your routing without a Play install, call InstallAttribution.debugOverride(referrer: ...) in a debug build; see the testing guide.

Example output #

The quick start above, run against the referrer utm_source=newsletter&utm_campaign=spring&utm_content=%2Fitems%2F42:

read(): Attributed(source: googlePlay, referrer: utm_source=newsletter&utm_campaign=spring&utm_content=%2Fitems%2F42, token: null, clickedAt: null)
routed: [/items/42]
second launch: claimOnce() -> null (handled)

referrer.deepLinkResult(allow: ['/items/']) says why a referrer has no link:

utm_content=%2Fitems%2F42                           -> DeepLinkFound(/items/42)
utm_content=%252Fitems%252F42                       -> DeepLinkRejected(looksDoubleEncoded)
utm_content=%2Fadmin                                -> DeepLinkRejected(notAllowlisted)
utm_content=https%3A%2F%2Fevil.example%2Fitems%2F1  -> DeepLinkRejected(hostNotAllowed)
utm_content=%2F%2Fevil.example                      -> DeepLinkRejected(unsafe)
utm_source=google-play&utm_medium=organic           -> DeepLinkRejected(absent)

Features #

Feature What you get
One typed result Attributed, Organic or Unavailable(reason) from a read() that never throws
No ANR Every Play Binder call runs on a background thread, with a timeout (default 10 s)
Typed failures DeadObjectException, PERMISSION_ERROR, a null bundle and more each get their own UnavailableReason
Consume once readOnce() / claimOnce(): first launch only, one claim per process, a persisted retry budget, upgrades skipped
Safe deep links deepLink(allow: [...]) decodes exactly once and only returns allowlisted paths
Installer detection InstallSource: Play, Galaxy Store, AppGallery, Amazon, side-load, restore
Link building PlayStoreUrl here, and wherefrom_builder for pure-Dart backends, both tested against shared JSON vectors
Built-in Kotlin No Kotlin Gradle Plugin line, so Flutter's KGP warning never names this plugin
Testable debugOverride() for debug builds, a replaceable platform for unit tests

Documentation #

Trademarks #

Google Play and Android are trademarks of Google LLC. Apple, App Store and iOS are trademarks of Apple Inc. This project is independent and is not affiliated with, sponsored by or endorsed by Google or Apple.

License #

MIT © saimskywalker. See LICENSE.

0
likes
160
points
639
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Flutter plugin: Play Install Referrer (Android) and Apple AdServices (iOS) for install attribution and deferred deep links, with typed results and first-launch-only reads.

Repository (GitHub)
View/report issues
Contributing

Topics

#flutter #android #ios #deep-linking #attribution

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on wherefrom

Packages that implement wherefrom