SDK Debug Settings
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 in Component Registry.
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:
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.
<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:
{
"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:
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.
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:
- Open your project in the Digia Engage dashboard and go to Test & Debug.
- Scan the QR code with the phone's camera — or copy the link shown below the QR and send it to the phone.
- Open the link — you'll land on a Digia page with a button to continue.
- 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:
# 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:
- You are testing a build that qualifies — see the note at the top of this page.
- On iOS,
digia-engageis inInfo.plistexactly as shown above. - The SDK is initialized — the screen cannot open before
Digia.initializeruns.
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:
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:
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:
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
import { Digia } from '@digia-engage/core';
Digia.openDebugSettings();
Digia.openDebugSettings(context)
Digia.presentDebugSettings(from: viewController)
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:
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:
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):
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 package:
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).