magic_deeplink 0.0.3
magic_deeplink: ^0.0.3 copied to clipboard
Deep linking support for Magic Framework (Universal Links, App Links).
Changelog #
All notable changes to this project will be documented in this file.
[Unreleased] #
0.0.3 - 2026-09-02 #
Fixed #
- A tapped push notification now opens its deep link. It never has before. The OneSignal wiring shipped in 0.0.1 and has been inert in every release since:
DeeplinkServiceProvider.boot()read the push driver'sonNotificationClickedand cast it toStream<Map<String, dynamic>>, a type it has never had (magic_notificationspublishesStream<PushNotificationEvent>), so the cast threw on every boot. It could not have got that far anyway, because the driver is attached in the notifications provider'sboot, which runs after this one in the order consumers register them, so the read that preceded the cast threw first. Both throws landed in acatchwhose body was two comment lines, which is why nobody noticed: no log, no exception, just a push that opened the app wherever it happened to be. The package's own test suite certified the feature green with a double whose stream really was aStream<Map<String, dynamic>>, and which handed out a driver before boot. - The wiring no longer depends on provider order or on a driver existing.
OneSignalDeeplinkHandler.setupnow subscribes to the notification manager's ownonPushClickedstream, which the manager owns from construction and republishes onto whenever a driver is attached, so notifications may boot before or after this package. That stream is also the subject-guarded one, so a push addressed to whoever held the device before does not open a deep link for whoever holds it now. An app withoutmagic_notificationsinstalled creates nothing here: no subscription, no timer, no handler. - A failure is reported instead of swallowed. A notification manager that publishes no
onPushClicked, an event carrying no readable payload, and an error on the click stream are each logged at error level through magic'sLog, guarded byMagic.bound('log')so a host that binds no logger is not handed a second failure on a path that is already degrading. There is no emptycatchleft in this package. - Resolving the notifications manager can no longer abort application boot.
app.make('notifications')runs the binding factory andonPushClickedis a getter, so either can throw; the handler answersNoSuchMethodErrorby name but aStateErrorout of an uninitialised manager is a different thing entirely. magic'sApplication.bootawaits providers in a bare loop with no error handling of its own, so an escaping throw here did not degrade the deep-link feature, it stopped the app booting and took every provider registered after this one with it, over a plugin that is OPTIONAL. The resolution is now guarded and reports at error level through the same seam. This is not the emptycatchthis release removed: that one had two comment lines for a body and is why the feature stayed inert for two years, while this one says what failed and only then lets boot continue. - A second
setupno longer leaves the first subscription live. The early return for a manager publishing no click stream sat before the cancel, so re-wiring against a manager this handler cannot follow left it routing taps through the previous one.
Added #
DeeplinkServiceProvider.dispose(), tearing down everythingboot()wired. The push-click handler was constructed inline inbootand the reference discarded, so thedisposethatdoc/basics/handlers.mdtells consumers to call in provider teardown was unreachable. The provider now holds the handler it wired, along with the driver's link subscription and the driver itself, and drops all three. Provider-level rather than handler-level on purpose: adispose()reaching only the push handler would read as tearing the provider down while leaving the driver running. It also callsDeeplinkManager.forgetDriver(), which existed and had no caller: the manager holds its own reference set byboot, so clearing the provider's field alone left the singleton answeringmanager.driverwith a driver this provider had just torn down. Adispose()that lands INSIDEboot()is covered too:await driver.initialize(...)suspends, and a teardown in that window used to be followed by boot resuming and subscribing anyway, creating a subscription after the teardown that nothing would ever cancel. And the scheduled initial-link read checks a disposed flag on both sides of its await, because there is no handle to cancel aFuture.delayedwith and a teardown in the same turn as boot would otherwise still route a deep link afterwards. Idempotent, because a consumer calling it does not know which parts a given deployment wired.
Changed #
OneSignalDeeplinkHandler.setup(manager, notifications)takes the notification manager, not a stream. The second parameter wasStream<Map<String, dynamic>>and is now themagic_notificationsmanager itself, read structurally (dynamic) so this package still declares no dependency on that one.extractData(event)is new and public: it reads a push event's payload off itsdatamember without naming the event's type.
Runtime requirement #
- The revived wiring reads
NotificationManager.onPushClicked, which arrives inmagic_notifications0.1.0 and does not exist below it. This package deliberately declares no dependency onmagic_notificationsat all (the coupling is optional; an app can use deep links with no push), so no resolver will ever enforce this version floor. That makes it a requirement only these words can carry: pairing this release with amagic_notificationsolder than 0.1.0 gets the handler's error-level report (the bound `notifications` manager ... publishes no `onPushClicked` stream) instead of a routed deep link, not a build failure.
0.0.2 - 2026-07-26 #
Changed #
magicconstraint bumped to^0.0.3->^0.0.5. The old bound excluded every magic release since 0.0.4: under pub's0.0.zcaret semantics^0.0.3means<0.0.4, so this plugin could not resolve alongside a consumer on current magic at all. Now tracks magic 0.0.5. No behavior change in this package.
0.0.1 - 2026-06-24 #
💥 Breaking Changes #
- Removed bin/ entrypoint:
dart run magic_deeplink:install/dart run magic_deeplink:generateno longer available. Use host-dispatched artisan commands instead:dart run <app>:artisan deeplink:installanddart run <app>:artisan deeplink:generate. This requires addingMagicDeeplinkArtisanProviderto your app's artisan providers list (see CLAUDE.md for setup). - Removed magic_cli dependency: Commands now extend
ArtisanCommandfromfluttersdk_artisaninstead ofCommandfrommagic_cli.
✨ Improvements #
- Manifest-driven install: The
deeplink:installcommand is now powered byinstall.yamland the artisan transactional installer, replacing imperative setup code. This enables consistent scaffolding across all magic plugins. - Read-only MCP tools: none. magic_deeplink ships only mutating commands (install, generate) and registers no MCP tools.
📚 Documentation #
- README: Rewrite to match Magic ecosystem format (centered logo, badges, features table, quick start)
- doc/ folder: Add comprehensive documentation (installation, configuration, drivers, handlers, CLI, architecture)
- CLAUDE.md: Updated architecture section and command table to reflect artisan dispatch model
🔧 Improvements #
- Package naming: Fix
fluttersdk_magic_deeplink→magic_deeplinkreferences for pub.dev publishing
0.0.1-alpha.1 - 2026-03-25 #
✨ Core Features #
- Unified Deep Link API: Single interface for iOS Universal Links and Android App Links
- Driver Pattern: Extensible driver architecture with
AppLinksDriveras default - Route Handler: Automatically maps deep link paths to Magic Routes via
RouteDeeplinkHandler - OneSignal Integration: Seamless notification click → deep link handling via
OneSignalDeeplinkHandler - CLI Tools:
installcommand generates config,generatecommand producesapple-app-site-associationandassetlinks.json - Service Provider:
DeeplinkServiceProviderfor automatic DI registration and boot