1# expo-dev-menu 2 3Expo/React Native module with the developer menu. 4 5# ⚙️ Installation 6 7Firstly, you need to add the `expo-dev-menu` package to your project. 8 9<details> 10<summary>yarn</summary> 11 12```bash 13yarn add expo-dev-menu 14``` 15 16</details> 17 18<details> 19<summary>npm</summary> 20 21```bash 22npm install expo-dev-menu 23``` 24 25</details> 26 27Then you can start to configure the native projects using steps below. 28 29## Android 30 311. Set up the `DevMenuManager` in the native code. 32 33 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. 34 35 - **Basic** 36 37 Open the `MainActivity.java` or `MainActivity.kt` and make sure that your main activity class extends the `DevMenuAwareReactActivity`. 38 39 <details> 40 <summary>Java</summary> 41 42 ```java 43 ... 44 // You need to import the `DevMenuAwareReactActivity` class 45 import expo.modules.devmenu.react.DevMenuAwareReactActivity; 46 ... 47 48 // Make sure that the `MainActivity` extends the `DevMenuAwareReactActivity` class not the `ReactActivity` 49 public class MainActivity extends DevMenuAwareReactActivity { 50 ... 51 } 52 ``` 53 54 </details> 55 <details> 56 <summary>Kotlin</summary> 57 58 ```kotlin 59 ... 60 // You need to import the `DevMenuAwareReactActivity` class 61 import expo.modules.devmenu.react.DevMenuAwareReactActivity; 62 ... 63 64 // Make sure that the `MainActivity` extends the `DevMenuAwareReactActivity` class not the `ReactActivity` 65 class MainActivity : DevMenuAwareReactActivity() { 66 ... 67 } 68 ``` 69 70 </details> 71 72 <br/> 73 74 - **Advanced** 75 76 77 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`. 78 79 <details> 80 <summary>Java</summary> 81 82 ```java 83 ... 84 // Add those imports. 85 import android.view.KeyEvent; 86 import android.view.MotionEvent; 87 88 import expo.modules.devmenu.DevMenuManager; 89 ... 90 91 public class MainActivity extends ReactActivity { 92 ... 93 // A function which sends the touch events to the dev menu. 94 @Override 95 public boolean dispatchTouchEvent(MotionEvent ev) { 96 DevMenuManager.INSTANCE.onTouchEvent(ev); 97 return super.dispatchTouchEvent(ev); 98 } 99 100 // A function which handles the key commands. 101 @Override 102 public boolean onKeyUp(int keyCode, KeyEvent event) { 103 return DevMenuManager.INSTANCE.onKeyEvent(keyCode, event) || super.onKeyUp(keyCode, event); 104 } 105 } 106 ``` 107 108 </details> 109 110 <details> 111 <summary>Kotlin</summary> 112 113 ```kotlin 114 ... 115 // Add those imports. 116 import android.view.KeyEvent; 117 import android.view.MotionEvent; 118 119 import expo.modules.devmenu.DevMenuManager; 120 ... 121 122 class MainActivity : ReactActivity() { 123 ... 124 // A function which sends the touch events to the dev menu. 125 override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { 126 DevMenuManager.onTouchEvent(ev) 127 return super.dispatchTouchEvent(ev) 128 } 129 130 // A function which handles the key commands. 131 override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { 132 return DevMenuManager.onKeyEvent(keyCode, event) || super.onKeyUp(keyCode, event) 133 } 134 } 135 ``` 136 137 </details> 138 139 140 <br/> 141 142 II. Open the `MainApplication` class (`MainApplication.java` or `MainApplication.kt`) and in `onCreate` method initialize `DevMenuManager`. 143 144 <details> 145 <summary>Java</summary> 146 147 ```java 148 ... 149 public class MainApplication extends Application implements ReactApplication { 150 ... 151 @Override 152 public void onCreate() { 153 ... 154 DevMenuManager.INSTANCE.initializeWithReactNativeHost(getReactNativeHost()); 155 } 156 } 157 ``` 158 159 </details> 160 161 162 <details> 163 <summary>Kotlin</summary> 164 165 ```kotlin 166 ... 167 public class MainApplication : Application(), ReactApplication { 168 ... 169 override fun onCreate() { 170 ... 171 DevMenuManager.initializeWithReactNativeHost(reactNativeHost); 172 } 173 } 174 ``` 175 176 </details> 177 178## iOS 179 1801. Add `expo-dev-menu` to your Podfile. 181 182 183 ```ruby 184 ... 185 target '<your app>' do 186 ... 187 pod 'EXDevMenu', path: '../node_modules/expo-dev-menu', :configurations => :debug 188 ... 189 end 190 ``` 191 1922. Run `pod install` in `ios` directory. 1933. Open file with your `AppDelegate` (`AppDelegate.m` or `AppDelegate.swift`) and pass bridge to the `DevMenuManager`. 194 195 <details> 196 <summary>Objective-C</summary> 197 198 ```objc 199 ... 200 // Firstly, you need to import EXDevMenu package. 201 #if defined(EX_DEV_MENU_ENABLED) 202 @import EXDevMenu; 203 #endif 204 ... 205 206 @implementation AppDelegate 207 ... 208 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 209 { 210 ... 211 RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; 212 RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge 213 moduleName:@"devMenuDemo" 214 initialProperties:nil]; 215 // Add those lines only if you aren't using the dev-launcher. 216 #if defined(EX_DEV_MENU_ENABLED) 217 [DevMenuManager configureWithBridge:bridge]; 218 #endif 219 } 220 @end 221 ``` 222 223 </details> 224 225 <details> 226 <summary>Swift</summary> 227 228 ```swift 229 ... 230 // Firstly, you need to import EXDevMenu package. 231 #if EX_DEV_MENU_ENABLED 232 @import EXDevMenu 233 #endif 234 ... 235 236 @UIApplicationMain 237 class AppDelegate: UMAppDelegateWrapper { 238 override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 239 ... 240 241 if let bridge = RCTBridge(delegate: self, launchOptions: launchOptions) { 242 ... 243 // Add those lines. 244 #if EX_DEV_MENU_ENABLED 245 DevMenuManager.configure(withBridge: bridge) 246 #endif 247 } 248 249 ... 250 } 251 } 252 ``` 253 254 </details> 255 256# Accessing the Developer Menu 257 258You 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). 259 260> **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. 261 262# Extending the dev-menu's functionalities 263 264One 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. 265 266The below instructions will show you how to create simple extension that removes a key from the `NSUserDefaults`/`SharedPreferences`. 267 268> **Note:** The tutorial was written using Kotlin and Swift. However, you can also use Java and Objective-C if you want. 269 270 <details> 271 <summary> Android</summary> 272 2731. Create a class which extends the `ReactContextBaseJavaModule` and implements the `DevMenuExtensionInterface`. 274 275 ```kotlin 276 // CustomDevMenuExtension.kt 277 package com.devmenudemo.customdevmenuextension 278 279 import com.facebook.react.bridge.ReactApplicationContext 280 import com.facebook.react.bridge.ReactContextBaseJavaModule 281 import expo.interfaces.devmenu.DevMenuExtensionInterface 282 import expo.interfaces.devmenu.items.DevMenuItem 283 284 class CustomDevMenuExtension(reactContext: ReactApplicationContext) 285 : ReactContextBaseJavaModule(reactContext), 286 DevMenuExtensionInterface { 287 288 override fun getName() = "CustomDevMenuExtension" // here you can provide name for your extension 289 290 override fun devMenuItems(): List<DevMenuItem>? { 291 // Firstly, create a function which will be called when the user presses the button. 292 val clearSharedPreferencesOnPress: () -> Unit = { 293 reactApplicationContext 294 .getSharedPreferences("your.shared.preferences", MODE_PRIVATE) 295 .edit() 296 .apply { 297 remove("key_to_remove") 298 Log.i("CustomDevMenuExt", "Remove key from SharedPreferences") 299 apply() 300 } 301 } 302 303 // Then, create `DevMenuAction` object. 304 val clearSharedPreferences = DevMenuAction( 305 actionId = "clear_shared_preferences", // This string identifies your custom action. Make sure that it's unique. 306 action = clearSharedPreferencesOnPress 307 ).apply { 308 label = { "Clear shared preferences" } // This string will be displayed in the dev menu. 309 glyphName = { "delete" } // This is a icon name used to present your action. You can use any icon from the `MaterialCommunityIcons`. 310 importance = DevMenuItemImportance.HIGH.value // This value tells the dev-menu in which order the actions should be rendered. 311 keyCommand = KeyCommand(KeyEvent.KEYCODE_S) // You can associate key commend with your action. 312 } 313 314 // Return created object. Note: you can register multiple actions if you want. 315 return listOf(clearSharedPreferences) 316 } 317 } 318 ``` 319 3202. Create a react native package class for the extension. 321 322 ```kotlin 323 // CustomDevMenuExtensionPackage.kt 324 package com.devmenudemo.customdevmenuextension 325 326 import android.view.View 327 import com.facebook.react.ReactPackage 328 import com.facebook.react.bridge.ReactApplicationContext 329 import com.facebook.react.uimanager.ReactShadowNode 330 import com.facebook.react.uimanager.ViewManager 331 332 class CustomDevMenuExtensionPackage : ReactPackage { 333 override fun createNativeModules(reactContext: ReactApplicationContext) = listOf( 334 CustomDevMenuExtension(reactContext) // here you need to export your custom extension 335 ) 336 337 override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<View, ReactShadowNode<*>>> = listOf() 338 339 } 340 ``` 341 3423. Go to `MainApplication` (`MainApplication.java` or `MainApplication.kt`) and register created package. 343 344 ```java 345 // MainApplication.java 346 347 // You need to import your custom package. 348 import com.devmenudemo.customdevmenuextension.CustomDevMenuExtensionPackage; // the package can be different in your case 349 350 ... 351 public class MainApplication extends Application implements ReactApplication { 352 ... 353 354 @Override 355 protected List<ReactPackage> getPackages() { 356 List<ReactPackage> packages = new PackageList(this).getPackages(); 357 358 // Add this line. 359 packages.add(new CustomDevMenuExtensionPackage()); 360 361 return packages; 362 } 363 } 364 ``` 365 366 </details> 367 368 <details> 369 <summary> iOS</summary> 370 3711. Create a Swift class which implements `DevMenuExtensionProtocol` for your extension. 372 373 ```swift 374 // CustomDevMenuExtension.swift 375 import EXDevMenuInterface 376 377 @objc(CustomDevMenuExtension) 378 open class CustomDevMenuExtension: NSObject, RCTBridgeModule, DevMenuExtensionProtocol { 379 public static func moduleName() -> String! { 380 return "CustomDevMenuExtension" // here you can provide name for your extension 381 } 382 383 @objc 384 open func devMenuItems() -> [DevMenuItem]? { 385 // Firstly, create a function which will be called when the user presses the button. 386 let clearNSUserDefaultsOnPress = { 387 let prefs = UserDefaults.standard 388 prefs.removeObject(forKey: "key_to_remove") 389 } 390 391 let clearNSUserDefaults = DevMenuAction( 392 withId: "clear_nsusersdefaults", // This string identifies your custom action. Make sure that it's unique. 393 action: clearNSUserDefaultsOnPress 394 ) 395 396 clearNSUserDefaults.label = { "Clear NSUserDefaults" } // This string will be displayed in the dev menu. 397 clearNSUserDefaults.glyphName = { "delete" } // This is a icon name used to present your action. You can use any icon from the `MaterialCommunityIcons`. 398 clearNSUserDefaults.importance = DevMenuItem.ImportanceHigh // This value tells the dev-menu in which order the actions should be rendered. 399 clearNSUserDefaults.registerKeyCommand(input: "p", modifiers: .command) // You can associate key commend with your action. 400 401 // Return created object. Note: you can register multiple actions if you want. 402 return [clearNSUserDefaults] 403 } 404 } 405 ``` 406 407 > **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](https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/importing_objective-c_into_swift). 408 4092. Create a `.m` file to integrate Swift class with react native and add the following lines. 410 411 ```objc 412 // CustomDevMenuExtension.m 413 414 #import <React/RCTBridgeModule.h> 415 416 @interface RCT_EXTERN_REMAP_MODULE(CustomDevMenuExtensionObjc, CustomDevMenuExtension, NSObject) 417 @end 418 ``` 419 4203. Add the following line into the bridging header. 421 422 ```objc 423 #import <React/RCTBridgeModule.h> 424 ``` 425 426 </details> 427 428After all those steps you should see something like this: 429 430 431 432# API 433 434```js 435import * as DevMenu from 'expo-dev-menu'; 436``` 437 438For now, the `DevMenu` module exports only one method - [`openMenu`](#openmenu). 439 440### openMenu() 441 442Opens the dev menu. 443 444#### Example 445 446Using 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). 447 448Below you can find an example of opening the dev menu on button press: 449 450```js 451import * as DevMenu from 'expo-dev-menu'; 452import { Button } from 'react-native'; 453 454export const DevMenuButton = () => ( 455 <Button 456 onPress={() => { 457 DevMenu.openMenu(); 458 }} 459 title="Press to open the dev menu " 460 color="#841584" 461 /> 462); 463``` 464 465# Contributing 466 467Contributions are very welcome! Please refer to guidelines described in the [contributing guide](https://github.com/expo/expo#contributing). 468