dynalinks 2.0.0
dynalinks: ^2.0.0 copied to clipboard
Dynalinks SDK for Flutter - Deferred deep linking and attribution for iOS and Android apps.
Changelog #
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
2.0.0 - 2026-08-01 #
Breaking #
- Raised the minimum supported version to Flutter 3.44 / Dart 3.12. This is required for both Swift Package Manager support and the UIScene life cycle APIs.
- A link that launched the app is now delivered only through
getInitialLink(), never through theonDeepLinkReceivedstream. Previously, if a stream listener happened to be attached before the link resolved, the launch link arrived there instead. If your app only listens to the stream, it will stop seeing cold-start links - you must also callgetInitialLink()on startup. Links that arrive while the app is running are unchanged and still come through the stream.
Added #
- Swift Package Manager support: the iOS plugin now ships a
Package.swiftalongside its podspec, so it builds under either dependency manager. Swift Package Manager is the default from Flutter 3.44 onwards, and the CocoaPods registry becomes read-only on 2 December 2026. - UIScene life cycle support: the plugin now implements
FlutterSceneLifeCycleDelegateand registers as a scene delegate, handlingscene(_:willConnectTo:options:)(cold start) andscene(_:continue:)(warm continuation). - The privacy manifest is now bundled with the plugin. It was previously present in the repository
but never shipped, because the podspec's
resource_bundlesentry was commented out.
Fixed #
- Cold-start links are no longer dropped, on either platform. When a link launched the app from
a terminated state, the platform delivered it before Dart had run and called
configure, so the plugin resolved it against an unconfigured SDK, the call failed withnotConfigured, and the link was discarded. The link is now held until the SDK is ready and resolved immediately afterwards.getInitialLink()waits for that resolution instead of racing it and reporting "no link" while the request is still in flight. On iOS this affected both life cycles; on Android the launching intent is read when the plugin attaches to the activity, which is likewise beforeconfigure. getInitialLink()no longer swallows the link when called frominitState. On Android it marked the initial link as consumed before checking whether one had arrived, so a call that beat the in-flight resolution returnednulland permanently discarded the link. It is now marked consumed only when a result is actually handed to Dart.- A link arriving while no listener is attached is no longer lost. It was held for
getInitialLink(), which reports "no link" once the launch link has been taken - so a link tapped while a listener was briefly detached, during a route change say, was unreachable on both channels. Such links are now held for the next listener and delivered when one attaches. - A slow first-launch link is no longer stranded. The wait that stops
getInitialLink()hanging used to give up sooner than the request beneath it could finish, and the link then landed in a slot the app had already been told was empty. The wait now outlasts the request, and a launch link that still arrives after it goes to the stream rather than nowhere. - A failed
configure()no longer throws away the cold-start link. The buffered link was the only copy, so an app that caught the error and configured again - after fetching its API key, say - had already lost it. Waiting calls are still answered immediately; if an app has already been told there is no initial link, the result of the retry is delivered on the stream instead. - Legacy iOS app-delegate cold starts still reach
getInitialLink(). Launch options now distinguish the launch continuation from a link that arrives while the app is running, so each reaches the correct channel. - The no-listener stream backlog is bounded. It retains the 32 most recent links and discards the oldest result when an app leaves the stream unsubscribed for an extended period.
- Android no longer drops a launch link when the engine outlives the activity. The launch
intent was read once per plugin instance, so with a cached engine, add-to-app or a
FlutterFragment, a tap that started a fresh activity never had its intent read. The guard is now keyed on the link itself. - Android no longer resolves intents that are not App Links. Any intent data was resolved, so a custom-scheme or content URI opened elsewhere in the app cost a network round trip and put a non-match on the stream. Only http and https are handled now, matching iOS.
- Android no longer stops working when its plugin instance is reattached to a new engine. The coroutine scope was cancelled on detach and never rebuilt, so every later call silently did nothing and left its Dart future unanswered.
- The same link arriving twice in quick succession is now delivered once, on both platforms.
- A second
getInitialLink()call no longer strands the first. On iOS, when a call arrived while an earlier one was still waiting for a link to resolve, it replaced the held reply and the firstFuturenever completed. All waiting calls are now answered. getInitialLink()no longer hangs when the link never arrives. A call made while a link was still buffered or resolving now waits for it on both platforms, and fails open withnullrather than waiting forever if the resolution never lands. Aconfigure()that fails answers waiting calls straight away instead of leaving them on that timeout.reset()no longer leaves a link resolution in flight. A resolution started beforereset()could land afterwards and repopulate the initial link, so a stale link surfaced after the SDK had been torn down.- Android no longer replays the launch link when the activity is recreated. The launching intent was re-read on every attach, so an activity recreation outside a configuration change delivered the cold-start link a second time.
checkForDeferredDeepLink()now reportsisDeferredastrueon Android, matching iOS. The result previously carried whatever the underlying SDK set.reset()now reports failures as aDynalinksExceptionlike every other method, instead of letting a raw platform error escape.- Universal Links no longer stop working on apps that have adopted the UIScene life cycle.
Incoming links were handled only via
application(_:continue:restorationHandler:), which iOS does not call once an app declares a scene manifest. Such apps received no deep links at all, with no error. Apps still on the app delegate life cycle are unaffected and keep working.
Changed #
- Migrated to Flutter's built-in Kotlin support: the Android build no longer applies the Kotlin
Gradle Plugin itself and uses
kotlin { compilerOptions { } }in place ofkotlinOptions { }. Applying KGP is deprecated in Flutter 3.44 and fails to build under AGP 9. - Updated the iOS SDK dependency to
~> 1.0.4. - The Android setup guide no longer tells you to set
launchMode. It previously prescribedsingleTask, which deep linking does not require - Flutter already generatessingleTop, and that is sufficient. The setup step is now just the intent filter. Whatever launch mode your app uses today keeps working.
1.0.4 - 2026-02-06 #
Changed #
- Updated Android SDK dependency to 1.0.3
- Adds support for deferred deep linking with full URL preservation in referrer parameter
- Improves unnamed link handling with query parameter preservation
1.0.3 - 2026-01-21 #
Fixed #
- CRITICAL: Updated iOS SDK dependency to require minimum version 1.0.3 (
~> 1.0.3)- Fixes build error: "Value of type 'DeepLinkResult.LinkData' has no member 'referrer'"
- Previous
~> 1.0allowed iOS SDK 1.0.2 which lacks attribution fields - Users should run
cd ios && pod update DynalinksSDKafter updating
1.0.2 - 2026-01-21 #
Added #
- Added attribution tracking fields to
LinkDatamodel:referrer- Referrer tracking parameter (e.g., "utm_source=facebook&utm_campaign=summer")providerToken- Apple Search Ads attribution token (pt parameter)campaignToken- Campaign identifier for attribution (ct parameter)
- Updated README with attribution tracking examples
- iOS plugin bridge now passes attribution fields from native SDK 1.0.3
- Android plugin bridge now passes attribution fields from native SDK 1.0.1
Changed #
- Updated Android SDK dependency to 1.0.1 (adds
iosDeferredDeepLinkingEnabledfield) - iOS SDK dependency remains at
~> 1.0(automatically resolves to 1.0.3)
1.0.1 - 2026-01-21 #
Fixed #
- Fixed TypeError when parsing nested maps from Android platform channels
- Android platform now properly handles
Map<Object?, Object?>toMap<String, dynamic>conversion for link data - Resolves:
type '_Map<Object?, Object?>' is not a subtype of type 'Map<String, dynamic>' in type casterror
1.0.0 - 2025-01-06 #
Added #
- Initial release of the Dynalinks Flutter SDK
- Deferred Deep Linking: Check for deep links that were clicked before app installation
- Universal Links (iOS): Automatic handling of incoming Universal Links
- App Links (Android): Automatic handling of incoming App Links
- Manual Link Resolution: Resolve any Dynalinks URL programmatically
Features #
Dynalinks.configure()- Initialize the SDK with your API keyDynalinks.checkForDeferredDeepLink()- Check for deferred deep links on first launchDynalinks.handleDeepLink(Uri)- Manually resolve a deep link URIDynalinks.getInitialLink()- Get the link that launched the app (cold start)Dynalinks.onDeepLinkReceived- Stream of incoming links while app is runningDynalinks.reset()- Reset SDK state (for testing)
Models #
DeepLinkResult- Result of deep link resolution with match confidenceLinkData- Complete link data including deep link value, URLs, and social metadataConfidence- Match confidence level (high, medium, low)DynalinksLogLevel- SDK logging verbosity levels
Exceptions #
NotConfiguredException- SDK not configured before useInvalidApiKeyException- Invalid API key providedSimulatorException- Running on iOS Simulator or Android EmulatorNetworkException- Network request failedServerException- Server returned an errorNoMatchException- No matching link found- Platform-specific exceptions for Android Install Referrer
Platform Support #
- iOS 16.0+
- Android API 21+ (Android 5.0 Lollipop)
- Flutter 3.3.0+