Test your integration
One link, one device, seven checks. This walks every delivery path end to end and shows the diagnostics you need when a step misbehaves. Create a test link in the OptoLink portal first (path /promo/flash-24h works); its canonical URL is https://links.yourdomain.com/acme/shortCode.
The plugin repo ships an example/ harness (auto-init, live links log, Resolve deferred and Reset install state buttons) you can run against your org for the same checks.
Printing a payload with debugPrint(data) gives you the assertions below:
OptoLinkData(path: /promo/flash-24h, matchType: DIRECT, confidence: MatchConfidence.exact, score: null, isDeferred: false)
1. Initialize and device ID
Run the app.
Expected: a non-null UUID deviceId, and INFO lines prefixed OptoLink [info] in the console (debug builds print the trail without a logger). A thrown OptoLinkError here means a config value the native side rejected (most often a malformed baseUrl) or a missing native plugin.
2. Direct open, app installed (warm)
Leave the app running or backgrounded, then tap the short URL from Messages or Notes (iOS) / Chrome (Android).
Expected: the app opens and onLink receives exactly one event: matchType: DIRECT, confidence: exact, isDeferred: false. linkId is set when the short code resolves server-side (it stays null if the backend was unreachable at resolve time).
3. Direct open, app closed (cold)
Force-quit the app, then tap the short URL again.
Expected: the app cold-starts, getInitialLink() returns the same DIRECT payload, and onLink fires zero events. If a DIRECT event reaches onLink on a cold start, you are testing a warm open by accident or running 1.x behavior.
4. Deferred match, iOS
- Uninstall the app (clears the once-per-install match marker).
- In Safari, open the short URL. The redirect page shows a store button; tapping it writes the attribution token to the clipboard before navigating.
- Install and launch the app.
- First launch shows the paste-permission dialog. Tap Allow; that is the expected flow (see deferred deep linking).
onLinkreceivesmatchType: CLIPBOARD,confidence: exact,isDeferred: true.
Simulator shortcut: the App Store is not installable there, so prime the pasteboard directly with the URL-plus-token payload the redirect page writes. A bare token does not pass the SDK's pasteboard gate:
xcrun simctl pbcopy booted "https://links.yourdomain.com/acme/shortCode?opl_click=YOUR_TOKEN"
5. Deferred match, Android
- Play install (primary). On a device without the app, tap the short URL in Chrome, then Get it on Google Play, then install from the listing. First launch:
onLinkreceivesmatchType: INSTALL_REFERRER,confidence: exact,isDeferred: true. Requires a real Play-managed install; emulators without the Play Store cannot exercise this leg. - Clipboard fallback. Tap the Open button on the redirect page before installing (the tap is what writes the clipboard), then sideload or install. Expected:
matchType: CLIPBOARD,confidence: exact,isDeferred: true.
6. Identity and events
await optoLink.setIdentity('usr_88213'); // true
await optoLink.trackEvent('test_event', properties: {'k': 'v'}); // true
final ok = await optoLink.trackEvent(
'oversize', properties: {'blob': 'x' * 11264});
// false: properties exceed the 10 KiB serialized cap;
// OptoLink.lastError.status == 400
7. No-match sanity
Fresh install with no prior click: resolveDeferredLink() returns null and OptoLink.lastError is also null — a genuine no-match is not a failure. A null with lastError set is a failed call.
Common verification mistakes
- Cached install state. The deferred match runs once per install; reusing an install silently shows nothing. Uninstall first, or use the example harness's Reset install state (kill and relaunch after).
- Entitlement changes without a rebuild. iOS entitlements ride the signed binary and hot reload does not propagate them.
flutter clean && flutter run. - Taps from inside Safari. A tap on a link rendered in a Safari page loads the redirect page (Apple's same-origin policy), not your app. Test from Messages, Notes, or the address bar. Expected, not a bug.
- Staged fingerprint matches. You cannot stage the probabilistic legs by hand; they fire only when clipboard and referrer are absent (for example, an App Store bounce before the copy). If a scored match shows up in your logs,
data.scoretells you its tier.
When a step fails
- Read
OptoLink.lastErrorafter the failed call:status,message,requestId,isConnectionError. - Read the INFO trail. It covers the init summary, the per-rung deferred decisions (what each leg read), per-attempt request lines, and the final link emission. Debug builds print it; wire a
loggerto ship it to your crash reporter. - Still stuck: troubleshooting.