Implementing Debug Menu (Hidden Menu) for Mobile App Testers
A tester wants to reset onboarding without reinstalling the app. Switch to staging. Force-trigger a push notification. Check the current feature flag state. Simulate a network error. All of these are tasks for Debug Menu: a hidden screen accessible only in the appropriate builds.
Debug Menu Architecture
Debug Menu is a separate screen (or set of screens) accessible via a hidden gesture or special entry point. Activation: shake gesture, long press on version in Settings, secret tap pattern on logo (5 taps).
Critically important: Debug Menu must be completely excluded from release builds. Not hidden, not behind a flag — explicitly excluded from compilation.
iOS — conditional compilation:
#if DEBUG || BETA
import UIKit
final class DebugMenuViewController: UIViewController {
// all debug menu code
}
#endif
Android — separate build flavor:
app/
src/
main/
debug/
kotlin/com/example/debug/DebugMenuActivity.kt
release/
// DebugMenuActivity is absent
Register DebugMenuActivity in debug/AndroidManifest.xml. It's not in the release manifest. Gradle automatically includes the right sources per flavor.
Useful Debug Menu Functions
Environment switcher. The most essential function: switch between Dev/Staging/Production without rebuilding. Implementation — UserDefaults/SharedPreferences storing the current environment, URLSessionConfiguration.default.protocolClasses or OkHttp interceptor reads it on each request. After switching — forced logout and session restart.
Managing feature flags. If using Firebase Remote Config or a custom flag system, Debug Menu allows locally overriding flag values. With Firebase it's remoteConfig.setDefaults([key: value]) — local defaults override remote values.
State reset. Buttons: "Reset onboarding", "Clear cache", "Reset UserDefaults", "Delete Keychain". This speeds up testing first-run flows 10x.
QA tools. Display current userId, device token (for push), API version, environment variables. "Copy device token" button — tester can immediately send test push via Firebase Console.
Error simulation. Checkbox "Network error mode" — OkHttp/URLSession interceptor starts returning errors for all requests. Test how the app handles offline state without disabling Wi-Fi.
Example Structure
DebugMenuScreen
├── Environment
│ ├── ● Dev (https://api-dev.example.com)
│ ○ Staging
│ ○ Production
├── Feature Flags
│ ├── new_checkout: [Remote] ON [Override] OFF ↺
│ └── ai_search: [Remote] OFF [Override] ON ↺
├── User
│ ├── User ID: abc-123 [Copy]
│ ├── Push Token: def-456 [Copy]
│ └── [Clear session]
├── Cache
│ ├── [Clear image cache] 482 MB
│ └── [Clear all caches]
└── Simulate
├── [Trigger push notification]
├── [Force network error]
└── [Crash app] ← for testing Crashlytics
Access and Security
If Debug Menu is needed in beta builds (TestFlight/Firebase App Distribution) but not in release — use separate build configuration: Debug, Beta, Release. In Beta configuration, compile Debug Menu but include pinning and other production security measures. This way testers work with near-production configuration but with debugging conveniences.
Never leave Debug Menu accessible via runtime flag in release (e.g., if BuildConfig.VERSION_NAME.contains("beta")). This is trivially bypassed.
Timeline
Basic Debug Menu with environment switcher, flag management, and state reset — 2–3 days. Extended with error simulation and custom tools — closer to 4 days.







