Adding a debug menu to mobile apps can save a considerable amount of both valuable development and testing time. It can reduce back-and-forths between the different teams, and cut down the hours wasted trying to hunt down mysterious bugs, whenever something goes wrong.
Whether it is a panel on the side or a separate screen triggered by a shake gesture, a debug menu is a way for developers to display important pieces of information on the UI for QA engineers or for themselves. It can also provide useful shortcuts, or expose feature toggles that can enable some powerful tools to simplify testing. All in all, it’s a toolbox for anything useful that end-users should not be concerned about.
This article, besides being a shameless plug for an Android library I’ve been working on for the past year, tries to give some ideas on what pieces of functionality to include in such a debug menu. All projects have different requirements, but the following ideas might be considered useful in many cases.
Just a quick note on the library mentioned above: Beagle (free and open-source) offers multiple UI implementations (side drawer, bottom sheet, dialog, activity, or view), that seamlessly integrate into your project. It displays a list of customizable Modules that can be changed at any time, providing a simple, plug-and-play solution to all the problems we are about to discuss. If you’re interested in a showcase app where you can try all these features, you can download it from the Play Store.
Information on the build
Some simple static texts can go a long way in avoiding confusion. The version number of the build should appear in the debug menu, maybe together with the build date: QA engineers should always know exactly which version they are testing. Other pieces of metadata, such as the package name of the application and the build type could also be useful. And let’s add a title as well, it looks better that way.
The Beagle library offers multiple modules that can be used to make static content look good, such as TextModule, LongTextModule, KeyValueListModule, DividerModule, and PaddingModule. However, for this case, the special formatting offered by the HeaderModule might be the best choice.
Ways to change the app configuration
The most obvious example for this category is a feature toggle or a backend environment switcher. However, all sorts of ideas can be considered for more specific use cases, e.g.: imagine a role-playing game where the statistics of the character can be changed from the debug menu, or a food delivery app where the payment method provider can be replaced with a fake one on demand.
In Beagle, there are many module types that follow the same general concept of providing a UI to change the value of some predefined configuration field. As a result, they have very similar public API-s, but they are distinguished based on the type of value they work with:
These modules all offer the option to persist the wrapped value on local storage as well as asking for confirmation before applying new values.
Ways to debug network events
Incorrect messaging between the server and the client app is a frequent source of problems. Unfortunately, debugging such issues is often a tedious choir: connecting the device to a computer so that logs can be copied and then formatted into a human-readable JSON, or hooking up a proxy to listen to all network communication. Displaying network requests and response logs in a nicely formatted way, directly in the app is the fastest method to identify the root of the problem.
This is very simple to do with Beagle: the NetworkLogListModule only needs one additional dependency and an extra line of code to hook into OkHttp or Ktor. It will then display all incoming and outgoing traffic in a list, where tapping on any element opens up a dialog with its formatted JSON payload. The individual tags of that JSON can be expanded/collapsed dynamically, and the library can also generate text log files that can be shared.
Similar to the previous topic, displaying certain logs on the UI that are unrelated to networking can also be useful. A good example is logging analytics events, so that QA engineers can instantly verify how tracking works, without ever opening an admin panel. Another useful debugging tool is logging the individual stages of a more complex under-the-hood flow, such as Bluetooth connection/communication.
With Beagle, multiple instances of LogListModule can be added to the debug menu, each displaying log categories of messages marked with different labels. Integrating with Timber is a piece of cake. The library also supports persisting logs across app restarts and, just like when logging network requests, a separate artifact makes this functionality usable even in pure Kotlin modules, that don’t depend on the Android SDK.
Test accounts to simplify authentication
Logging in and out of different accounts takes up a great amount of time and is a process that is repeated a lot during the testing of certain apps. So, why not display a list of test accounts that work as a shortcut for filling in the authentication details with a single tap?
The ItemListModule offers an easy-to-use API to achieve this. All you need to do is to provide a list of test accounts by implementing the BeagleListItemContract interface through a data class that holds an email address and a password. Also, since Beagle modules can be added or removed at any time, this feature can be tied to the login screen’s lifecycle to have a clutter-free debug menu.
Shortcuts to relevant system settings
The App Info page from the device settings app is a frequent destination during testing: it’s used for clearing the app data, revoking permissions, changing notification settings, and many more. The Developer Options page is another useful screen from system settings that is often accessed. So why not save a few taps and add shortcuts to these directly into the app?
With Beagle, these specialized buttons can easily be added to the configuration by instantiating AppInfoButtonModule or DeveloperOptionsButtonModule. Furthermore, a generic button is also a very useful tool, as developers can implement any custom action through it. To achieve this, simply create an instance of TextModule, specifying the Button type and a click listener lambda in its constructor.
Tools to check the UI
Creating pixel-perfect UI is a never-ending struggle, but some tools can make verifying the layouts at runtime a bit easier. Again, these are functionalities that can be achieved with third-party solutions or with developer quick toggles, but bundling them with the app makes them more accessible and opens the door for project-specific customizations.
For example, the KeylineOverlaySwitchModule, when enabled, draws a grid over the screen which can be used to check View alignments. The exact dimensions of this grid and its guidelines can be set to the specific values used by the project’s design, in case they don’t match the defaults coming from the Material standards. Another useful tool is the AnimationDurationSwitchModule, which can be used to slow down animations: some transition glitches are hard to spot otherwise.
Tools to check in-app navigation
The ability to see the order in which the various screens’ lifecycle events are getting called can shine a light on many potential issues. However, the most important advantage of such a feature is probably during the onboarding of new developers: just imagine how useful it is for somebody unfamiliar with the codebase to see the class names for every screen while navigating within the app.
The LifecycleLogListModule automatically tracks configurable lifecycle events for every Fragment and Activity and displays them in a list.
Ways to take screenshots / record the screen
Obviously, everyone can do this without a debug menu. But hear me out. Have you ever looked into the screenshot gallery of a QA device? Well, good luck finding what you’re looking for amongst the hundreds of images and videos taken from dozens of different apps. Bundling this functionality with the application will result in a nicely organized Gallery from which all relevant files can be shared. Again, I can’t stress this enough, the setup is just one line of code with Beagle.
The ScreenCaptureToolboxModule allows users to take screenshot images or screen recording videos, as well as to open a customizable Gallery screen that, just like anything else introduced by the library, matches the application theme. The Gallery only shows images and videos taken from the current app (organized by date) and allows the sharing of these media files without requesting storage permission. However, users have to give their consent before taking a screenshot/screen recording, as other apps might be visible (e.g. in split-screen mode) – this is a security restriction coming from the system.
Information on the device
While most people know what kind of phone they have, it’s often useful to display information such as the manufacturer, OS version, or screen resolution.
Arguably, the benefits might not be worth the effort to set this up from scratch, but with Beagle, this can be done in one line of code, simply by adding a DeviceInfoModule to the configuration.
Hidden features to simplify testing
It’s difficult to come up with examples that apply to multiple projects but think of skipping a purchase flow in a streaming platform or teleporting around the map in a game.
The predefined modules from Beagle that can be part of this category are the ForceCrashButtonModule which can be used to test crash reporting and the LoremIpsumGeneratorButtonModule which creates random placeholder texts.
A way to report bugs
While the ultimate goal of a debug menu is to offer tools for finding problems, it should also simplify the way these issues are reported after they have been found. This can be as simple as providing an email address. However, a fully-fledged bug reporting UI with the ability to attach screenshots, logs, and device/build information can probably prove more useful in the long run. This solution should also integrate crash reporting: on debug builds where crashes are more frequent, it’s easier to see the stack trace right on the UI, than connecting the device to a computer.
The BugReportButtonModule links to a separate screen that shows a configurable form (users can attach media files, crash logs, lifecycle logs, network, custom logs, and device/build information to their issue description), at the end of which a zip archive is generated. This screen is also automatically opened whenever the app crashes. The generated file can be shared with the development team via the standard system share sheet by default, but custom implementations can directly upload or process it.
One more thing: security
A debug menu often allows ways to take control over the application or leak implementation details which is very useful during testing, but it may prove to be dangerous if it was to fall into the wrong hands. As a result, it is of paramount importance that no part of it makes its way into the codebase of a production release.
Beagle solves this by offering a noop implementation that uses the very same public API (so that the same source code can be compiled with it as with the real implementation). Nonetheless, it does so with empty function bodies, basically not doing anything. For extra security, consider wrapping calls to Beagle in conditions that verify the build type. That way ProGuard can completely remove all the code related to your configuration.
After having added debug menus into basically every project I could get my hands on for the past year, I became convinced that any mobile app could benefit from them, from the simplest festival clients to complex online games. While the list above contains the main ideas I have found useful, no two projects have the same set of requirements, and there are certainly lots of great ideas that would only work for some.
Beagle offers a powerful feature set that can be used to create debug menus containing anything I could think of, and more. If you’re interested in giving it a try, check out its repository on GitHub or download this showcase app from the Play Store. I’m very much interested in expanding its usefulness, as well as this list. So if you have any ideas, don’t hesitate to let me know by leaving a comment here, dropping a line via email, or opening an issue on GitHub!