wherefrom 0.3.0
wherefrom: ^0.3.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.
0.3.0 #
- Renamed from
imaji_install_attribution. To migrate, replace the dependency (imaji_install_attribution: ^0.2.2→wherefrom: ^0.3.0) and the import (package:imaji_install_attribution/imaji_install_attribution.dart→package:wherefrom/wherefrom.dart). Test fakes extendWherefromPlatform, formerlyImajiInstallAttributionPlatform. The method channel is nowio.github.saimskywalker/wherefrom, so the old and new packages never share a native side if both end up in one app. No other API change. The consume-once marker, the failed-read count and the first-start record keep their file names, so an upgraded app keeps its state. - Native names follow: Android package
io.github.saimskywalker.wherefrom, plugin classWherefromPluginon Android and iOS, CocoaPods pod and Swift packagewherefrom. The repository is nowgithub.com/saimskywalker/wherefromand the plugin lives inpackages/wherefrom. - CI builds release versions: an R8-shrunk Android app bundle that runs on
an emulator, and iOS device builds with Swift Package Manager and with
CocoaPods.
doc/merged-manifest.snapshot.xmllists what the plugin adds to an app's merged manifest (#34).
0.2.2 #
- The plugin moved to
packages/imaji_install_attributionin the repository, so the published archive holds only this package and the iOS example builds from a clone in any folder (#44, #15). Therepository:URL points there. To trackmainwith a git dependency, addpath: packages/imaji_install_attribution. The shared test vectors are now underpackages/imaji_install_attribution/test/vectors/. No API change. - README: shorter, with a verified quick start and example output. The
detailed guides moved to
doc/(API, consume once, decoding rule, migration, setup, FAQ) andCONTRIBUTING.md.
0.2.1 #
- README: an Alternatives section replaces the comparison table, and "Why this exists" describes the platform's traps.
- README: the migration notes map Play's own fields and response codes.
- README: a Trademarks section for the Google and Apple names.
- The package description leads with what the plugin does.
0.2.0 #
Behaviour change: readOnce() no longer re-delivers an old referrer to
an install that existed before the plugin did (#30).
readOnce({treatUpgradesAsHandled = true}): on Android, an install that was updated since it was installed, and whose first start with the plugin came after that update, is marked handled and getsnull. Before, the first launch after an app adopted the plugin in an update re-delivered Play's referrer, up to 90 days old, to every existing user. The plugin now records its first start innoBackupFilesDir. Passfalsefor the 0.1 behaviour.- An install that started on 0.1.x counts as adopted in an update on its
first 0.2.0 launch (0.1.x kept no first-seen record) and is marked
handled. It already had its first launch under 0.1.x. Pass
treatUpgradesAsHandled: falseif an app on 0.1.x still needs that install's referrer.
- An install that started on 0.1.x counts as adopted in an update on its
first 0.2.0 launch (0.1.x kept no first-seen record) and is marked
handled. It already had its first launch under 0.1.x. Pass
readOnce({maxClickAge}): a Play click older than the limit is marked handled. UsesclickedAtServerwhen reported, elseclickedAt; an unknown click time is never stale. Default: no limit.readOnce({pastInstallFlow}): the app's own "past the install flow" check;truemarks the install handled without a Play read. README recipe: require signed in and a first run completed on this device, with the first-run flag kept out of backups.InstallAttribution.lastSkipReason(ReadOnceSkip) says whyreadOnce()returnednull.InstallAttribution.adoptExistingState(alreadyHandled:): one-time migration from referrer handling an app shipped before the plugin.InstallAttribution.installFacts()andInstallFacts. Platform implementations get a defaultinstallFacts()that returnsnull.- Android:
markHandled()keeps an existing marker instead of rewriting it. - iOS:
treatUpgradesAsHandledandmaxClickAgeare no-ops. InstallAttribution.claimOnce()returns anInstallClaim(complete()/release()) (#31):- One claim per process: a concurrent
claimOnce()returnsnull(claimedElsewhere). - A persisted failed-read budget,
maxFailedReads(default 3). After it the install is marked handled and Play is not asked again. terminalreasons (default{playStoreMissing}) mark the install handled at once.- The count lives next to the handled marker, outside backups, and
clearHandled()clears it. failedReads(),lastFailure, and theReadOnceSkipvaluesclaimedElsewhere,readFailed,terminalFailureandbudgetSpent.- Platform interface:
failedReads()/recordFailedRead(). A platform without them gets the lock but no budget.
- One claim per process: a concurrent
- Android: the Play Store presence check (
PackageManager.getPackageInfo, a Binder call) moved from the platform thread to the worker thread (#31). No Binder call inreadruns on the main thread now. - README quick start uses
claimOnce(). InstallReferrer.deepLinkResult()/resolveDeepLinkResult()return a sealedDeepLinkResult:DeepLinkFound(uri), orDeepLinkRejected(reason)with aDeepLinkRejection(#29). The reasons areabsent,unsafe,undecodable,notAPath,hostNotAllowed,looksDoubleEncodedandnotAllowlisted. A double-encoded value is reported, never repaired.deepLink()isdeepLinkResult().uriOrNull.InstallReferrer(raw, decoding: ReferrerDecoding.rfc3986): opt-in RFC 3986 value decoding, where+stays+.ReferrerDecoding.form(application/x-www-form-urlencoded) stays the default.withDecoding()re-reads the same raw string. Equality now includes the decoding.- Fix: a
.or..segment is refused as written.Uri.parsenormalised/items/../adminto/admin, and/items/../items/42to/items/42(accepted), before the check could see the segment. test/vectors/deep_link_vectors.json: language-neutral vectors for the decoding rule and the link builder. The README documents the rule and shows how a backend in any language can test against the file.
0.1.2 #
- Fix:
InstallReferrer.deepLink()andInstallReferrer.resolveDeepLink()never throw. A value with a percent escape that is not UTF-8 (%FF,%C3, a truncated%E2%82) threwFormatExceptioninstead of returningnull; it is now refused, like a malformed escape (%,%ZZ), whichUri.parseused to rewrite silently into%25. The check covers the query and fragment too, so a returned link's ownqueryParameterscan no longer throw. Unescaped non-ASCII text (/items/café) is still accepted; a lone UTF-16 surrogate is refused. A fuzz test over every%XXescape and random input pins it, and a differential test against the 0.1.1 algorithm checks nothing else changed.
0.1.1 #
Documentation only; no API or behaviour change.
- README: the package is on pub.dev. Install with
flutter pub add imaji_install_attribution(orimaji_install_attribution: ^0.1.0); the Git dependency is now only the way to track unreleasedmain. Adds pub version, pub points, likes, CI and license badges. - README: one cell of the comparison table corrected.
- RELEASING.md is a reusable per-release checklist.
0.1.0 #
First release.
InstallAttribution.read({timeout})returns a sealedInstallAttributionResult—Attributed,OrganicorUnavailable(reason)— and never throws.- Android: Google Play Install Referrer 2.2. The Binder call runs off the
platform thread with a timeout; a null details bundle,
DeadObjectException,PERMISSION_ERROR,FEATURE_NOT_SUPPORTED,SERVICE_UNAVAILABLE,DEVELOPER_ERROR,SERVICE_DISCONNECTED(after one reconnect) and a missing Play Store each map to a typedUnavailableReason. - Android:
InstallSource— installer package viagetInstallSourceInfo(API 30+) orgetInstallerPackageName, mapped to Play, Galaxy Store, AppGallery, Amazon, side-load or restore. - iOS: Apple AdServices attribution token as
Attributed(source: appleAds, token: ...); the simulator reportsUnavailable(notSupported). Swift Package Manager and CocoaPods, with a privacy manifest. InstallReferrer.deepLink(allow: [...]): decodes exactly once and only returns allowlisted in-app paths.PlayStoreUrl: store links whose referrer is encoded exactly once.- Opt-in consume-once store:
readOnce(),markHandled(),isHandled(),clearHandled(). debugOverride()for debug builds, and a testing guide (TESTING.md).- Built on AGP Built-in Kotlin: no Kotlin Gradle Plugin in any build file. Minimum supported SDK: Flutter 3.44 / Dart 3.12.