Skip to main content

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.

LaunchWhere it arrivesonLink events
Cold start on a link (app was closed)getInitialLink()zero
Warm open on a link (app running or backgrounded)onLinkone
Deferred match on first launch after installonLinkone, 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.path is the link's destination (e.g. /promo/flash-24h) and data.params its query parameters; the full shape is in the API reference.
  • confidence tells you how the link arrived: DIRECT opens carry exact, deferred clipboard and referrer matches carry exact, fingerprint matches carry high or medium, IP-only matches carry low.
  • Neither surface throws. getInitialLink() returns null on channel failure with OptoLink.lastError set; 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.