What is expo-dev-menu?
The expo-dev-menu package provides a developer menu for React Native apps built with Expo. It allows developers to access various debugging and development tools directly within the app, making it easier to test and debug applications.
What are expo-dev-menu's main functionalities?
Access Developer Menu
This feature allows developers to open the developer menu programmatically. The developer menu provides various tools and options for debugging and testing the app.
import { DevMenu } from 'expo-dev-menu';
// Open the developer menu
DevMenu.show();
Custom Developer Menu Items
This feature allows developers to add custom items to the developer menu. These items can perform specific actions when selected, providing a way to extend the functionality of the developer menu.
import { registerDevMenuItem } from 'expo-dev-menu';
registerDevMenuItem({
name: 'Custom Item',
callback: () => {
console.log('Custom item selected');
}
});
Toggle Performance Monitor
This feature allows developers to toggle the performance monitor on and off. The performance monitor provides insights into the app's performance, such as frame rate and memory usage.
import { DevMenu } from 'expo-dev-menu';
// Toggle the performance monitor
DevMenu.togglePerformanceMonitor();
Other packages similar to expo-dev-menu
react-native-debugger
React Native Debugger is a standalone app for debugging React Native apps. It includes a range of tools for inspecting and debugging the app, such as a network inspector, Redux DevTools, and more. Unlike expo-dev-menu, it is a separate application that runs alongside the React Native app.
react-native-flipper
Flipper is a platform for debugging mobile apps, including React Native apps. It provides a range of plugins for inspecting and debugging various aspects of the app, such as network requests, layout, and more. Flipper is more comprehensive than expo-dev-menu, offering a wider range of debugging tools and integrations.
Expo/React Native module with the developer menu.
βοΈ Installation
Firstly, you need to add the expo-dev-menu
package to your project.
yarn
yarn add expo-dev-menu
npm
npm install expo-dev-menu
Then you can start to configure the native projects using steps below.
π€ Android
-
Set up the DevMenuManager
in the native code.
You can do it in two ways. We recommend using the basic initialization. However, if you have the custom activity in your application, then the advanced one will be more suitable for you.
-
Basic
Open the MainActivity.java
or MainActivity.kt
and make sure that your main activity class extends the DevMenuAwareReactActivity
.
Java
...
import expo.modules.devmenu.react.DevMenuAwareReactActivity;
...
public class MainActivity extends DevMenuAwareReactActivity {
...
}
Kotlin
...
import expo.modules.devmenu.react.DevMenuAwareReactActivity;
...
class MainActivity : DevMenuAwareReactActivity() {
...
}
-
Advanced
I. Open the file with the main activity of your application (MainActivity.java
or MainActivity.kt
) and add methods that will communicate with the DevMenuManager
.
Java
...
import android.view.KeyEvent;
import android.view.MotionEvent;
import expo.modules.devmenu.DevMenuManager;
...
public class MainActivity extends ReactActivity {
...
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
DevMenuManager.INSTANCE.onTouchEvent(ev);
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
return DevMenuManager.INSTANCE.onKeyEvent(keyCode, event) || super.onKeyUp(keyCode, event);
}
}
Kotlin
...
import android.view.KeyEvent;
import android.view.MotionEvent;
import expo.modules.devmenu.DevMenuManager;
...
class MainActivity : ReactActivity() {
...
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
DevMenuManager.onTouchEvent(ev)
return super.dispatchTouchEvent(ev)
}
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
return DevMenuManager.onKeyEvent(keyCode, event) || super.onKeyUp(keyCode, event)
}
}
II. Open the MainApplication
class (MainApplication.java
or MainApplication.kt
) and in onCreate
method initialize DevMenuManager
.
Java
...
public class MainApplication extends Application implements ReactApplication {
...
@Override
public void onCreate() {
...
DevMenuManager.INSTANCE.initializeWithReactNativeHost(getReactNativeHost());
}
}
Kotlin
...
public class MainApplication : Application(), ReactApplication {
...
override fun onCreate() {
...
DevMenuManager.initializeWithReactNativeHost(reactNativeHost);
}
}
π iOS
-
Add expo-dev-menu
to your Podfile.
...
target '<your app>' do
...
pod 'EXDevMenu', path: '../node_modules/expo-dev-menu', :configurations => :debug
...
end
-
Run pod install
in ios
directory.
-
Open file with your AppDelegate
(AppDelegate.m
or AppDelegate.swift
) and pass bridge to the DevMenuManager
.
Objective-C
...
#if defined(EX_DEV_MENU_ENABLED)
@import EXDevMenu;
#endif
...
@implementation AppDelegate
...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"devMenuDemo"
initialProperties:nil];
#if defined(EX_DEV_MENU_ENABLED)
[DevMenuManager configureWithBridge:bridge];
#endif
}
@end
Swift
...
#if EX_DEV_MENU_ENABLED
@import EXDevMenu
#endif
...
@UIApplicationMain
class AppDelegate: UMAppDelegateWrapper {
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
if let bridge = RCTBridge(delegate: self, launchOptions: launchOptions) {
...
#if EX_DEV_MENU_ENABLED
DevMenuManager.configure(withBridge: bridge)
#endif
}
...
}
}
You can access the developer menu by shaking your device, making a three-finger long-press gesture, or by selecting "Shake Gesture" inside the Hardware menu in the iOS simulator. You can also use keyboard shortcuts - βD
on iOS, or βM
on Android when you're using Mac OS and Ctrl+M
on Windows and Linux. Alternatively for Android, you can run the command adb shell input keyevent 82
to open the dev menu (82
being the Menu key code).
Note: if you're using the iOS simulator and keyboard shortcuts don't work, make sure you've selected Send Keyboard Input to Device
inside the I/O
menu in the Simulator.
One of the main purposes of this package was to provide an easy way to create extensions. We know that developing a React Native app can be painful - often, developers need to create additional tools, which for example, clear the local storage, to test or debug their applications. Some of their work can be integrated with the application itself to save time and make the development more enjoyable.
The below instructions will show you how to create simple extension that removes a key from the NSUserDefaults
/SharedPreferences
.
Note: The tutorial was written using Kotlin and Swift. However, you can also use Java and Objective-C if you want.
π€ Android
-
Create a class which extends the ReactContextBaseJavaModule
and implements the DevMenuExtensionInterface
.
package com.devmenudemo.customdevmenuextension
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import expo.interfaces.devmenu.DevMenuExtensionInterface
import expo.interfaces.devmenu.items.DevMenuItem
class CustomDevMenuExtension(reactContext: ReactApplicationContext)
: ReactContextBaseJavaModule(reactContext),
DevMenuExtensionInterface {
override fun getName() = "CustomDevMenuExtension"
override fun devMenuItems(): List<DevMenuItem>? {
val clearSharedPreferencesOnPress: () -> Unit = {
reactApplicationContext
.getSharedPreferences("your.shared.preferences", MODE_PRIVATE)
.edit()
.apply {
remove("key_to_remove")
Log.i("CustomDevMenuExt", "Remove key from SharedPreferences")
apply()
}
}
val clearSharedPreferences = DevMenuAction(
actionId = "clear_shared_preferences",
action = clearSharedPreferencesOnPress
).apply {
label = { "Clear shared preferences" }
glyphName = { "delete" }
importance = DevMenuItemImportance.HIGH.value
keyCommand = KeyCommand(KeyEvent.KEYCODE_S)
}
return listOf(clearSharedPreferences)
}
}
-
Create a react native package class for the extension.
package com.devmenudemo.customdevmenuextension
import android.view.View
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ReactShadowNode
import com.facebook.react.uimanager.ViewManager
class CustomDevMenuExtensionPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext) = listOf(
CustomDevMenuExtension(reactContext)
)
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<View, ReactShadowNode<*>>> = listOf()
}
-
Go to MainApplication
(MainApplication.java
or MainApplication.kt
) and register created package.
import com.devmenudemo.customdevmenuextension.CustomDevMenuExtensionPackage;
...
public class MainApplication extends Application implements ReactApplication {
...
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new CustomDevMenuExtensionPackage());
return packages;
}
}
π iOS
-
Create a Swift class which implements DevMenuExtensionProtocol
for your extension.
import EXDevMenuInterface
@objc(CustomDevMenuExtension)
open class CustomDevMenuExtension: NSObject, RCTBridgeModule, DevMenuExtensionProtocol {
public static func moduleName() -> String! {
return "CustomDevMenuExtension"
}
@objc
open func devMenuItems() -> [DevMenuItem]? {
let clearNSUserDefaultsOnPress = {
let prefs = UserDefaults.standard
prefs.removeObject(forKey: "key_to_remove")
}
let clearNSUserDefaults = DevMenuAction(
withId: "clear_nsusersdefaults",
action: clearNSUserDefaultsOnPress
)
clearNSUserDefaults.label = { "Clear NSUserDefaults" }
clearNSUserDefaults.glyphName = { "delete" }
clearNSUserDefaults.importance = DevMenuItem.ImportanceHigh
clearNSUserDefaults.registerKeyCommand(input: "p", modifiers: .command)
return [clearNSUserDefaults]
}
}
Note: if you don't use Swift in your project earlier, you need to create bridging header. For more information, checks importing objective-c into swift.
-
Create a .m
file to integrate Swift class with react native and add the following lines.
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_REMAP_MODULE(CustomDevMenuExtensionObjc, CustomDevMenuExtension, NSObject)
@end
-
Add the following line into the bridging header.
#import <React/RCTBridgeModule.h>
After all those steps you should see something like this:
![Final result](https://github.com/expo/expo/raw/HEAD/custom_dev_menu_extension_example.png)
π API
import * as DevMenu from 'expo-dev-menu';
For now, the DevMenu
module exports only one method - openMenu
.
Opens the dev menu.
Example
Using this method you can open the dev menu from your JS code whenever you want. It does nothing when the dev menu is not available (i.e. in release mode).
Below you can find an example of opening the dev menu on button press:
import * as DevMenu from 'expo-dev-menu';
import { Button } from 'react-native';
export const DevMenuButton = () => (
<Button
onPress={() => {
DevMenu.openMenu();
}}
title="Press to open the dev menu π"
color="#841584"
/>
);
π Contributing
Contributions are very welcome! Please refer to guidelines described in the contributing guide.