# Installation

Detour provides a Flutter plugin (`detour_flutter_plugin`) that bridges the native Detour SDKs on Android and iOS, so both builds share one Dart integration.

:::tip[AI agent skills]
Detour ships two AI coding-agent skills: `detour-onboarding` for a setup from scratch, and `migrate-to-detour` for moving off Branch or AppsFlyer. Both cover this page. See [Detour Skills](https://detour.swmansion.com/docs/platform/fundamentals/skills).
:::

A Detour app in the Dashboard is required before the SDK can resolve links. See [Getting Started](https://detour.swmansion.com/docs/platform/fundamentals/getting-started).

## Requirements

- Flutter: `>=3.3.0`
- Dart: `^3.11.1`
- Android min SDK: `24`
- iOS: `13.0+`

Links are processed on **Android and iOS** only. On web, macOS, Linux, and Windows the plugin loads a no-op so a multi-platform codebase keeps building. See [Platform support](#platform-support).

## Install package

Add the package to your `pubspec.yaml`:

```yaml title="pubspec.yaml"
dependencies:
  detour_flutter_plugin: ^1.2.1
```

Then run:

```bash
flutter pub get
```

## Native SDK dependencies

`detour_flutter_plugin` wraps the native Detour SDKs:

- The Android bridge depends on `com.swmansion.detour:detour-sdk:1.1.0`.
- The iOS bridge depends on the `Detour` CocoaPod (`~> 1.1.0`), pulled in automatically by the plugin podspec.

Keep `google()` and `mavenCentral()` in your project repositories so Gradle can resolve the Android dependency.

After changing iOS dependencies, run:

```bash
cd ios
pod install
cd ..
```

:::note[iOS privacy manifest]
The `Detour` pod ships a `PrivacyInfo.xcprivacy` manifest from `1.1.1`, and the
`~> 1.1.0` constraint above resolves to it, so a fresh `pod install` picks it up
with no extra work. A project whose `Podfile.lock` still pins `1.1.0` does not
have it. Run `pod update Detour` to move to `1.1.1`.

You still need to answer the App Store Connect privacy questionnaire for what
Detour collects. The answers are listed in the
[iOS SDK privacy reference](https://detour.swmansion.com/docs/sdk/ios/privacy#app-store-connect-questionnaire) and
apply unchanged to a Flutter app.
:::

## Credentials

Both values come from **API configuration** in the Dashboard: the `appID` and the publishable `apiKey`. See [Getting Started](https://detour.swmansion.com/docs/platform/fundamentals/getting-started#api-configuration).

Pass your credentials to `DetourConfig`:

```dart
const DetourConfig(
  apiKey: '<REPLACE_WITH_YOUR_API_KEY>',
  appID: '<REPLACE_WITH_APP_ID_FROM_PLATFORM>',
  shouldUseClipboard: true,
  linkProcessingMode: LinkProcessingMode.all,
)
```

Keep `apiKey` and `appID` in environment variables or another secure configuration layer.

## Platform integration

The plugin wires up the bridge and runtime listeners, but your app still needs
native deep-link registration. The Dashboard generates the Associated Domains
and App Links configuration for verified HTTPS links. It does not generate
custom-scheme registration.

### Android

#### Verified App Links

Paste the generated App Links filter into the `MainActivity` declaration in
`android/app/src/main/AndroidManifest.xml`:

```xml title="android/app/src/main/AndroidManifest.xml"
<activity
    android:name=".MainActivity"
    android:exported="true"
    android:launchMode="singleTop">
    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https" />
        <data android:host="<your-host>" />
        <data android:pathPrefix="/<app-hash>" />
        <!-- The Dashboard adds any required legacy short-link prefixes. -->
    </intent-filter>
</activity>
```

Use the exact host and paths generated in **App configuration**. New short links
reuse `/<app-hash>` and do not require another manifest entry or native build.

If your app is served from a [custom domain](https://detour.swmansion.com/docs/platform/advanced/custom-domain),
the Dashboard generates the filter for that domain. Assign the domain before
you copy the values. Changing the host an app is assigned to later needs a new
build. Do not add hosts it did not generate: on
Android 11 and lower that breaks App Link verification for every host at once,
see [Verification](https://detour.swmansion.com/docs/platform/fundamentals/universal-app-links#verification).

#### Custom-scheme links (optional)

Add a separate filter to the same activity only if you use links such as
`myapp://product/42`:

```xml title="android/app/src/main/AndroidManifest.xml"
<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="myapp" />
</intent-filter>
```

#### Callback delivery

No Android callback override is required. The plugin registers itself as a
Flutter `NewIntentListener`, processes the activity's initial intent, and
receives later intents from the Flutter embedding.

### iOS

#### Universal Links

In Xcode, open the Runner target, select **Signing & Capabilities**, add the
**Associated Domains** capability, and enter `applinks:<your-host>`. The
resulting `Runner.entitlements` entry is:

```xml title="ios/Runner/Runner.entitlements"
<key>com.apple.developer.associated-domains</key>
<array>
  <string>applinks:<your-host></string>
</array>
```

With a [custom domain](https://detour.swmansion.com/docs/platform/advanced/custom-domain), the Dashboard
generates this entry for that domain too.

#### Custom-scheme links (optional)

If you use scheme links, register the scheme under the Runner target's
**Info** → **URL Types**, or add it directly to `ios/Runner/Info.plist`:

```xml title="ios/Runner/Info.plist"
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>myapp</string>
    </array>
  </dict>
</array>
```

#### Delegate forwarding

Flutter plugins, Detour included, receive lifecycle callbacks through the Flutter delegate chain. If you override an iOS deep-link callback, return the `super` result:

```swift title="ios/Runner/AppDelegate.swift"
override func application(
  _ application: UIApplication,
  continue userActivity: NSUserActivity,
  restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
  return super.application(
    application,
    continue: userActivity,
    restorationHandler: restorationHandler
  )
}

override func application(
  _ app: UIApplication,
  open url: URL,
  options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
  return super.application(app, open: url, options: options)
}
```

If your project defines a custom `SceneDelegate`, subclass
`FlutterSceneDelegate` (recommended) or forward scene events through
`FlutterSceneLifeCycleProvider`. An app using Flutter's default scene delegate
does not need a custom class or callback overrides.

If your `FlutterSceneDelegate` subclass overrides either link callback, call
`super` so Flutter can forward it to Detour:

```swift title="ios/Runner/SceneDelegate.swift"
override func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
  super.scene(scene, continue: userActivity)
}

override func scene(
  _ scene: UIScene,
  openURLContexts URLContexts: Set<UIOpenURLContext>
) {
  super.scene(scene, openURLContexts: URLContexts)
}
```

After changing either native target, rebuild the app. A Dart hot reload does not
apply entitlements, `Info.plist`, or manifest changes.

Reference implementations in the example app:

- [AppDelegate.swift](https://github.com/software-mansion-labs/detour-flutter-plugin/blob/main/example/ios/Runner/AppDelegate.swift)
- [SceneDelegate.swift](https://github.com/software-mansion-labs/detour-flutter-plugin/blob/main/example/ios/Runner/SceneDelegate.swift)

:::note[Clipboard reads on iOS 14+]
Clipboard matching checks `detectPatterns` before it touches the pasteboard. The clipboard value is read only when a probable URL is detected.
:::

## Platform support

The plugin processes links on **Android** and **iOS**. On every other platform, that is web, macOS, Linux, and Windows, it loads a no-op implementation, so apps that target several platforms build and run without conditional imports:

- `configure(...)` does nothing and logs `Detour: current platform is not supported, skipping initialization`.
- `resolveInitialLink()` returns a result with `processed: true` and no link, so your startup flow continues normally.
- `processLink(...)` returns a result with `processed: false`.
- `linkStream` is an empty stream.
- `logEvent(...)` and `logRetention(...)` do nothing.

Call the SDK unconditionally from shared code. The no-op keeps unsupported platforms safe.

## Next steps

1. Wire up the startup and runtime flow in [Usage](https://detour.swmansion.com/docs/sdk/flutter/sdk-usage).
2. Start from the runnable app in [Examples](https://detour.swmansion.com/docs/sdk/flutter/examples).
3. Check the result. See [Testing & Troubleshooting](https://detour.swmansion.com/docs/sdk/flutter/testing).