# SDK Debug Settings

URL: https://www.digia.tech/docs/developer/debug-settings-deeplink

The Digia Engage SDK includes a debug settings screen for development and QA builds. This screen gives you access to SDK-side tools such as **Sync** , **Live test** connection status, the Digia bubble, recording mode, and other debugging utilities that may be added over time.

You need this screen when you want to:

- connect a debug device to the Digia Engage dashboard
- enable Sync for live campaign testing
- sync pages, anchors, and slots with the Digia Engage dashboard
After Sync sends pages, anchors, and slots to the dashboard, campaign creators can review them inComponent Registry .

warning
This screen only opens in builds meant for development and testing:
- **Android** — debuggable builds. Not release-signed builds, including Google Play internal-track.
- **iOS** — development builds, the simulator, and **TestFlight** . Not App Store builds.

## The Debug Settings Link

One fixed link opens the screen on every platform:

```text
digia-engage://_digia/debug-settings
```

The QR code on the dashboard's **Test & Debug** page encodes this link. You never need to wire your app's own deep linking for it — the SDK handles the link itself.

### Android

Nothing to set up. This applies to native Android, Flutter, and React Native — the SDK registers its own entry point for the link.

### iOS

Add the `digia-engage` scheme to your app's `Info.plist` . This is the only setup step, and iOS requires it — an app can only receive a URL scheme it declares itself.

```xml
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLName</key>
    <string>com.digia.engage.debug</string>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>digia-engage</string>
    </array>
  </dict>
</array>
```

`CFBundleURLSchemes` is what routes the link to your app. `CFBundleURLName` is just a label — any unique string works.

Leave the entry in for all builds. It is safe in production, where the SDK ignores the link.

- React Native - Flutter
For a React Native app, the file is `ios/YourApp/Info.plist` . On Expo, declare it in `app.json` instead — `expo prebuild` generates the `Info.plist` from it:
```json
{
  "expo": {
    "ios": {
      "infoPlist": {
        "CFBundleURLTypes": [
          {
            "CFBundleURLName": "com.digia.engage.debug",
            "CFBundleURLSchemes": ["digia-engage"]
          }
        ]
      }
    }
  }
}
```

Your `AppDelegate` also needs React Native's standard linking forwarding. The Expo and React Native templates already include it. If yours does not, add:
```swift
public override func application(
  _ app: UIApplication,
  open url: URL,
  options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
  return RCTLinkingManager.application(app, open: url, options: options)
}
```

For a Flutter app, the file is `ios/Runner/Info.plist` . No `AppDelegate` changes, as long as yours extends `FlutterAppDelegate` (the Flutter default). If it does not, forward the `UIApplicationDelegate` callbacks as you already do for your other plugins.

info
**Flutter on Android:** you do not need `flutter_deeplinking_enabled` . If you set it to `false` because another package owns your deep links, leave it — this link works either way.

## Test The Setup

Test on a debug build (or, on iOS, a TestFlight build).

For a real phone:

1. Open your project in the Digia Engage dashboard and go to **Test & Debug** .
2. Scan the QR code with the phone's camera — or copy the link shown below the QR and send it to the phone.
3. Open the link — you'll land on a Digia page with a button to continue.
4. Tap **Open app** . If prompted to choose an app, select the app being tested.
If the SDK debug settings screen opens, setup is complete.

For an emulator or simulator, open the link directly:

```bash
# Android emulator
adb shell am start -a android.intent.action.VIEW -d "digia-engage://_digia/debug-settings"

# iOS simulator
xcrun simctl openurl booted "digia-engage://_digia/debug-settings"
```

## Troubleshooting

Symptom: **the link opens your app, but the debug settings screen does not appear.**

First confirm the basics:

1. You are testing a build that qualifies — see the note at the top of this page.
2. On iOS, `digia-engage` is in `Info.plist` exactly as shown above.
3. The SDK is initialized — the screen cannot open before `Digia.initialize` runs.
If those check out, the usual cause is another library consuming the link before the SDK sees it:

### Flutter — app_links, uni_links, or go_router

On Android, these packages coexist with the Digia link — nothing to do. (Your link handler may also see `_digia/debug-settings` ; ignore it, or filter it with `Digia.isDebugSettingsDeepLink(uri)` .)

On iOS, the system offers the URL to each plugin in turn and stops at the first one that reports it handled it. Some link packages claim *every* URL rather than only the ones they recognise. If yours does, it takes the Digia link before the SDK sees it.

If you hit that, open the screen from the listener you already have:

```dart
void _handleLink(Uri uri) {
  if (Digia.isDebugSettingsDeepLink(uri)) {
    Digia.openDebugSettings(navigatorKey.currentContext!);
    return;
  }
  // your existing routing
}

_appLinks.uriLinkStream.listen(_handleLink);

// Cold start arrives separately, not through the stream.
final initial = await _appLinks.getInitialLink();
if (initial != null) _handleLink(initial);
```

`navigatorKey` is the same `GlobalKey<NavigatorState>` you pass to `MaterialApp` . Calling `Digia.openDebugSettings` when the screen is already open is safe — you will never get two.

### React Native — another SDK in AppDelegate

Your JS router is never the problem — React Native's `Linking` broadcasts every URL to every listener, so React Navigation, Expo Router, and the Digia SDK all coexist.

The problem can only be native. Branch, Firebase Dynamic Links, AppsFlyer, and the Facebook SDK each add their own handling to `AppDelegate` , and their setup snippets usually return as soon as they claim a URL:

```swift
public override func application(
  _ app: UIApplication,
  open url: URL,
  options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
  if SomeSDK.application(app, open: url, options: options) {
    return true                                                       // returns here…
  }
  return RCTLinkingManager.application(app, open: url, options: options) // …so this never runs
}
```

Most of those SDKs return `false` for URLs they do not own, and the chain continues. But if yours returns `true` for every URL it is handed, the Digia link stops there and never reaches the SDK.

Give `RCTLinkingManager` the URL first, so no other handler can short-circuit it:

```swift
public override func application(
  _ app: UIApplication,
  open url: URL,
  options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
  let handledByReactNative = RCTLinkingManager.application(app, open: url, options: options)
  return SomeSDK.application(app, open: url, options: options) || handledByReactNative
}
```

Both handlers still run — only the early return is gone.

## Open From Inside Your App

The deep link is one way in, not the only way. You can open the same screen from anywhere in your own app — a developer menu, an app settings page, or a shake gesture.

### From a button

If your app already has an internal developer or QA menu, add a button that calls the SDK directly:

- React Native - Android - iOS (Swift) - Flutter

```tsx
import { Digia } from '@digia-engage/core';

Digia.openDebugSettings();
```

```kotlin
Digia.openDebugSettings(context)
```

```swift
Digia.presentDebugSettings(from: viewController)
```

```dart
Digia.openDebugSettings(context);
```

These calls follow the same rule as the deep link: in a build that does not qualify (see the top of this page), they do nothing. That makes them safe to leave in shipped code.

### On shake

The SDK does not watch for shakes itself — another tool in your app may already use that gesture. If you want shake-to-open, wire it up in a few lines:

- React Native - Android - iOS (Swift) - Flutter
Uses [react-native-shake](https://www.npmjs.com/package/react-native-shake) :
```tsx
import { useEffect } from 'react';
import RNShake from 'react-native-shake';
import { Digia } from '@digia-engage/core';

useEffect(() => {
  const subscription = RNShake.addListener(() => Digia.openDebugSettings());
  return () => subscription.remove();
}, []);
```

No library needed — a small accelerometer listener:
```kotlin
class ShakeToDebugSettings(private val context: Context) : SensorEventListener {
    private var lastTriggerMs = 0L

    fun start() {
        val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
        val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) ?: return
        sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI)
    }

    override fun onSensorChanged(event: SensorEvent) {
        val gX = event.values[0] / SensorManager.GRAVITY_EARTH
        val gY = event.values[1] / SensorManager.GRAVITY_EARTH
        val gZ = event.values[2] / SensorManager.GRAVITY_EARTH
        val gForce = sqrt(gX * gX + gY * gY + gZ * gZ)
        val now = SystemClock.elapsedRealtime()
        if (gForce > 2.7f && now - lastTriggerMs > 1_000) {
            lastTriggerMs = now
            Digia.openDebugSettings(context)
        }
    }

    override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) = Unit
}

// e.g. in your Application or base Activity:
ShakeToDebugSettings(this).start()
```

No library needed — override `motionEnded` in any view controller (or a base class):
```swift
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
    super.motionEnded(motion, with: event)
    guard motion == .motionShake else { return }
    Digia.presentDebugSettings(from: self)
}
```

Uses the [shake](https://pub.dev/packages/shake) package:
```dart
import 'package:shake/shake.dart';

// e.g. in your root widget's initState:
ShakeDetector.autoStart(
  onPhoneShake: (_) => Digia.openDebugSettings(navigatorKey.currentContext!),
);
```

`navigatorKey` is the same `GlobalKey<NavigatorState>` you pass to `MaterialApp` .

Because `openDebugSettings` is a no-op in production builds, a stray shake there shows nothing. If you'd rather not run an accelerometer listener in production at all, guard the registration with your platform's debug check ( `__DEV__` , `BuildConfig.DEBUG` , `kDebugMode` , `#if DEBUG` ).
