# Testing & Troubleshooting

Deferred flow testing has platform constraints: full attribution path depends on store redirect chain.

## Deferred flow constraints

- Deferred matching is first-launch oriented.
- Runtime callbacks do not re-trigger deferred matching logic.
- The Flutter bridge does not expose a session reset API.

## Recommended deferred test flow

1. Uninstall the app from the device or simulator.
2. Open a Detour link.
3. Install and launch the app.
4. Verify the initial intent and the resolved route in your UI logs.
5. Relaunch the app and confirm the deferred behavior is not repeated.

## Runtime link tests

### Android App Link

```bash
adb shell am start -a android.intent.action.VIEW \
  -d "https://<your-link-domain>/<link-token>" \
  com.example.app
```

### Android custom scheme

```bash
adb shell am start -a android.intent.action.VIEW \
  -d "myapp://product/42" \
  com.example.app
```

### iOS custom scheme (simulator)

```bash
xcrun simctl openurl booted "myapp://product/42"
```

### iOS Universal Link (simulator)

```bash
xcrun simctl openurl booted "https://<your-link-domain>/<link-token>"
```

## Platform-specific notes

### iOS

:::note[Copy the link before installing]
Clipboard matching requires that you copy the link to your clipboard before installing. Ensure `shouldUseClipboard: true` is set in `DetourConfig` and that the copy-link feature is enabled in the Detour Dashboard for your iOS configuration.
:::

### Android

Install referrer `click_id` is often unavailable in local/dev-store scenarios. In that case matching falls back to probabilistic flow.

For local install referrer testing workarounds, see
[How to test the Play Store Install Referrer API](https://medium.com/@madicdjordje/how-to-test-the-play-store-install-referrer-api-78a63d59945b).

:::note[Missing `click_id`]
If you encounter click_id missing in your production build see limitations section [Play Store referrer missing](https://detour.swmansion.com/docs/platform/architecture/architecture-limitations#play-store-referrer-missing)
:::

### Testing Android App Links

If an installed app does not open Detour links, the Dashboard SHA-256 probably does not match the key that signed **the build on your device**:

| How you install the app                     | SHA-256 to use                | Where to find it                                                                |
| :---------------------------------------------- | :-------------------------------- | :---------------------------------------------------------------------------------- |
| Google Play, any track                      | Play App Signing key          | Play Console → **Protected with Play** → **Manage Play app signing** → *App signing key certificate* |
| `flutter run` / `flutter build apk --debug` | AGP default debug keystore    | `keytool -list -v -keystore ~/.android/debug.keystore -storepass android`, or `./gradlew signingReport` |
| Release build with your own keystore        | Your release keystore         | `keytool -list -v -keystore <path-to-your.keystore>`                              |

The Flutter template signs `release` with the debug config until you add your own release keystore, so a fresh `flutter build apk --release` is still debug-signed.

To check any build directly: `apksigner verify --print-certs build/app/outputs/flutter-apk/app-debug.apk`. The complete procedure is described in [Finding your Android SHA-256 fingerprint](https://detour.swmansion.com/docs/platform/fundamentals/universal-app-links#finding-your-android-sha-256-fingerprint).

## Common issues

<details id="no-match-found">
<summary>No match found (`404`)</summary>

- Check timestamps. The click may be older than the match window (15 minutes).
- Confirm the click was recorded in your Detour Dashboard.
- On the Android deterministic flow, confirm `click_id` is present in the install referrer.

</details>

<details id="click-limit-error">
<summary>App/Universal link never arrives despite a valid click</summary>

Your plan's click limit may be exceeded. The plugin surfaces nothing in Dart, because the intent never arrives, so check the native log for `[Detour:CLICK_LIMIT_ERROR]`. See [Click limits](https://detour.swmansion.com/docs/sdk/flutter/sdk-usage#click-limits).

</details>

<details id="too-many-requests">
<summary>`429 Too Many Requests`</summary>

Detour limits deferred link matching per device and IP address, and per app.
Wait between reinstall tests on one device. For tests on many
devices at once, create a separate app for testing in the Dashboard, because
each app has its own credentials and its own limit.

</details>

<details id="not-configured">
<summary>`NOT_CONFIGURED` platform error</summary>

`resolveInitialLink()` and `processLink()` raise it on both platforms. On
Android, `logEvent()` and `logRetention()` raise it too.

**Cause** — the method ran before a successful `configure(...)` call.

**Fix** — await `DetourService.start(...)` or
`DetourFlutterPlugin.configure(...)` before you call these methods. If
`configure(...)` failed, fix that error first.

</details>

<details id="invalid-args">
<summary>`INVALID_ARGS` platform error</summary>

Android only. `configure(...)` raises it when `apiKey` or `appID` is empty,
`processLink(...)` when `url` is empty, and `logRetention(...)` when `eventName`
is empty. A value that contains only whitespace counts as empty.

**Cause** — a required string argument is empty.

**Fix** — pass non-empty values. For where `apiKey` and `appID` come from, see
[Credentials](https://detour.swmansion.com/docs/sdk/flutter/sdk-installation#credentials).

</details>

<details id="no-activity">
<summary>`NO_ACTIVITY` platform error</summary>

**Cause** — on Android, `configure(...)` ran in a Flutter engine that has no
attached activity, such as a separate engine started for background work.

**Fix** — call `DetourService.start(...)` or
`DetourFlutterPlugin.configure(...)` from your app's UI code, not from a
background handler.

</details>

<details id="native-error">
<summary>`NATIVE_ERROR` platform error</summary>

Android only. `resolveInitialLink()` and `processLink()` raise it, and
`linkStream` emits it as an error event, when the native SDK fails to process a
link. `DetourService.start()` throws the same error and leaves
`isInitialLinkProcessed` at `false`, so catch it if your startup flow waits for
that flag.

A failed deferred match request on first launch raises it. When the server
responded, the message starts with the HTTP status code:

- `[401]` — the `apiKey` or `appID` is wrong. See
  [Credentials](https://detour.swmansion.com/docs/sdk/flutter/sdk-installation#credentials).
- `[402]` — your organization is over its plan's click limit. See
  [Click limits](https://detour.swmansion.com/docs/sdk/flutter/sdk-usage#click-limits).
- `[429]` — too many match requests. See
  [`429 Too Many Requests`](#too-many-requests).
- No status code — the request did not complete. Check the network connection.

</details>

<details id="ios-clipboard-not-read">
<summary>iOS clipboard not read</summary>

- Ensure `shouldUseClipboard: true` is set in `DetourConfig`.
- Ensure the copy-link feature is enabled in the Detour Dashboard for your iOS app configuration.
- The link must be copied to the clipboard before the app is opened, because the clipboard is only read during `resolveInitialLink()`.

</details>

<details id="deferred-link-not-returned">
<summary>Deferred link not returned on subsequent test runs</summary>

Deferred matching is first-launch based. Reinstall the app for a clean re-test.

</details>

<details id="buildconfig-defined-multiple-times">
<summary>Android release build fails with `Type com.swmansion.detour.BuildConfig is defined multiple times`</summary>

Debug builds of the same project succeed.

**Cause** — `detour_flutter_plugin` `1.1.1` and `1.2.0` use the Android
namespace `com.swmansion.detour`, which the native Detour SDK also uses. The
release build then contains two classes named
`com.swmansion.detour.BuildConfig`.

**Fix** — upgrade to `detour_flutter_plugin: ^1.2.1` and run `flutter pub get`.

</details>

## Debugging

- Android:
  - `adb logcat | grep -i detour`
- iOS simulator:
  - `xcrun simctl spawn booted log stream --predicate 'subsystem == "com.swmansion.detour"'`

For full runnable setup and commands, use [Examples](https://detour.swmansion.com/docs/sdk/flutter/examples).