Handle deep links
With the setup from install & initialize in place, the plugin needs no runtime wiring from you: the native SDK ingests Universal Links (iOS) and App Links (Android), resolves them against the backend, and hands Dart the result through exactly one of two surfaces.
This page covers direct links. Links for users who install the app after clicking are deferred matches and have their own page: deferred deep linking.
Where a link arrives
| Launch | Where it arrives | onLink events |
|---|---|---|
| Cold start on a link (app was closed) | getInitialLink() | zero |
| Warm open on a link (app running or backgrounded) | onLink | one |
| Deferred match on first launch after install | onLink | one, replayed to the first subscriber |
The rule behind the table: a cold-start direct link is cached for getInitialLink() and never streams; every deferred or warm emission streams on onLink, which replays its last emission to a late subscriber. getInitialLink() is idempotent, so reading it more than once is safe.
Only https links are ingested on both platforms. A custom URI scheme still routes the redirect page but delivers no payload (a 2.0.0 change from 1.x).
Subscribe and route
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
// Warm opens and deferred matches.
widget.optoLink.onLink.listen(_route);
// Cold starts: read once after init; null when this launch
// was not a deep link.
widget.optoLink.getInitialLink().then(_route);
}
void _route(OptoLinkData? data) {
if (data == null) return;
if (data.confidence == MatchConfidence.low) {
// IP-only match: confirm with the user before navigating.
return;
}
Navigator.of(context).pushNamed(data.path, arguments: data.params);
}
}
What the code relies on:
getInitialLink()returns native-resolved data, not a URL to parse.data.pathis the link's destination (e.g./promo/flash-24h) anddata.paramsits query parameters; the full shape is in the API reference.confidencetells you how the link arrived:DIRECTopens carryexact, deferred clipboard and referrer matches carryexact, fingerprint matches carryhighormedium, IP-only matches carrylow.- Neither surface throws.
getInitialLink()returnsnullon channel failure withOptoLink.lastErrorset; the stream drops malformed payloads with a warning.
What iOS and Android do with the tap
The OS decides whether your app gets the tap. Messages, Notes, and Mail engage Universal Link routing on iOS; a tap on a link rendered inside Safari loads the redirect page instead (Apple's same-origin policy), which is expected, not a bug. On Android, the autoVerify intent-filter must verify before taps open the app directly; check it with adb shell pm get-app-links. Both behaviors, and how to test each, are in test your integration.