4.5.8 • Published 5 days ago

expo-dev-menu v4.5.8

Weekly downloads
56
License
MIT
Repository
github
Last release
5 days ago

📦 expo-dev-menu

Expo/React Native module with the developer menu.

⚙️ Installation

Firstly, you need to add the expo-dev-menu package to your project.

yarn add expo-dev-menu
npm install expo-dev-menu

Then you can start to configure the native projects using steps below.

🤖 Android

  1. Add the dev-menu to the android project.

    I. Open settings.gradle and add the following lines:

    include(":expo-dev-menu")
    project(":expo-dev-menu").projectDir = new File("../node_modules/expo-dev-menu/android")
    
    include(":expo-dev-menu-interface")
    project(":expo-dev-menu-interface").projectDir = new File("../node_modules/expo-dev-menu-interface/android")

    II. Go to the build.gradle of your application and add expo-dev-menu as a dependency:

    dependencies {
      ...
      implementation project(":expo-dev-menu-interface")
      implementation project(":expo-dev-menu")
      ...
    }

    Note: You don't have to use implementationDebug to add expo-dev-menu only to the debug builds. This package will be removed from the release build automatically.

  2. 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.

      ...
      // You need to import the `DevMenuAwareReactActivity` class
      import expo.modules.devmenu.react.DevMenuAwareReactActivity;
      ...
      
      // Make sure that the `MainActivity` extends the `DevMenuAwareReactActivity` class not the `ReactActivity`
      public class MainActivity extends DevMenuAwareReactActivity {
        ...
      }
      ...
      // You need to import the `DevMenuAwareReactActivity` class
      import expo.modules.devmenu.react.DevMenuAwareReactActivity;
      ...
      
      // Make sure that the `MainActivity` extends the `DevMenuAwareReactActivity` class not the `ReactActivity`
      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`.

  <details>
  <summary>Java</summary>

  ```java
  ...
  // Add those imports.
  import android.view.KeyEvent;
  import android.view.MotionEvent;

  import expo.modules.devmenu.DevMenuManager;
  ...

  public class MainActivity extends ReactActivity {
    ...
    // A function which sends the touch events to the dev menu.
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
      DevMenuManager.INSTANCE.onTouchEvent(ev);
      return super.dispatchTouchEvent(ev);
    }

    // A function which handles the key commands.
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
      return DevMenuManager.INSTANCE.onKeyEvent(keyCode, event) || super.onKeyUp(keyCode, event);
    }
  }
  ```

  </details>

  <details>
  <summary>Kotlin</summary>

  ```kotlin
  ...
  // Add those imports.
  import android.view.KeyEvent;
  import android.view.MotionEvent;

  import expo.modules.devmenu.DevMenuManager;
  ...

  class MainActivity : ReactActivity() {
    ...
    // A function which sends the touch events to the dev menu.
    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
      DevMenuManager.onTouchEvent(ev)
      return super.dispatchTouchEvent(ev)
    }

    // A function which handles the key commands.
    override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
      return DevMenuManager.onKeyEvent(keyCode, event) || super.onKeyUp(keyCode, event)
    }
  }
  ```

  </details>


  <br/>

  II. Open the `MainApplication` class (`MainApplication.java` or `MainApplication.kt`) and in `onCreate` method initialize `DevMenuManager`.

  <details>
  <summary>Java</summary>

  ```java
  ...
  public class MainApplication extends Application implements ReactApplication {
    ...
    @Override
    public void onCreate() {
      ...
      DevMenuManager.INSTANCE.initializeWithReactNativeHost(getReactNativeHost());
    }
  }
  ```

  </details>


  <details>
  <summary>Kotlin</summary>

  ```kotlin
  ...
  public class MainApplication : Application(), ReactApplication {
    ...
    // A function which sends the touch events to the dev menu.
    override fun onCreate() {
      ...
      DevMenuManager.initializeWithReactNativeHost(reactNativeHost);
    }
  }
  ```

  </details>

🍏 iOS

  1. Add expo-dev-menu to your Podfile.
  ```ruby
  ...
  target '<your app>' do
    ...
    pod 'EXDevMenuInterface', path: '../node_modules/expo-dev-menu-interface'
    pod 'EXDevMenu', path: '../node_modules/expo-dev-menu', :configurations => :debug
    ...
  end
  ```
  1. Run pod install in ios directory.
  2. Open file with your AppDelegate (AppDelegate.m or AppDelegate.swift) and pass bridge to the DevMenuManager.

    ...
    // Firstly, you need to import EXDevMenu package.
    #if __has_include(<EXDevMenu/EXDevMenu-umbrella.h>)
    @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];
      // Add those lines.
      #if __has_include(<EXDevMenu/EXDevMenu-umbrella.h>)
      [DevMenuManager configureWithBridge:bridge];
      #endif
    }
    @end
    ...
    // Firstly, you need to import EXDevMenu package.
    #if canImport(EXDevMenu)
    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) {
          ...
          // Add those lines.
          #if canImport(EXDevMenu)
          DevMenuManager.configure(withBridge: bridge)
          #endif
        }
    
        ...
      }
    }

💪 Extending the dev-menu's functionalities

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.

  1. Create a class which extends the ReactContextBaseJavaModule and implements the DevMenuExtensionInterface.

    // CustomDevMenuExtension.kt
    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" // here you can provide name for your extension
    
      override fun devMenuItems(): List<DevMenuItem>? {
        // Firstly, create a function which will be called when the user presses the button.
        val clearSharedPreferencesOnPress: () -> Unit = {
          reactApplicationContext
            .getSharedPreferences("your.shared.preferences", MODE_PRIVATE)
            .edit()
            .apply {
              remove("key_to_remove")
              Log.i("CustomDevMenuExt", "Remove key from SharedPreferences")
              apply()
            }
        }
    
        // Then, create `DevMenuAction` object.
        val clearSharedPreferences = DevMenuAction(
          actionId = "clear_shared_preferences", // This string identifies your custom action. Make sure that it's unique.
          action = clearSharedPreferencesOnPress
        ).apply {
          label = { "Clear shared preferences" } // This string will be displayed in the dev menu.
          glyphName = { "delete" } // This is a icon name used to present your action. You can use any icon from the `MaterialCommunityIcons`.
          importance = DevMenuItemImportance.HIGH.value // This value tells the dev-menu in which order the actions should be rendered.
          keyCommand = KeyCommand(KeyEvent.KEYCODE_S) // You can associate key commend with your action.
        }
    
        // Return created object. Note: you can register multiple actions if you want.
        return listOf(clearSharedPreferences)
      }
    }
  2. Create a react native package class for the extension.

    // CustomDevMenuExtensionPackage.kt
    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) // here you need to export your custom extension
        )
    
        override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<View, ReactShadowNode<*>>> = listOf()
    
    }
  3. Go to MainApplication (MainApplication.java or MainApplication.kt) and register created package.

    // MainApplication.java
    
    // You need to import your custom package.
    import com.devmenudemo.customdevmenuextension.CustomDevMenuExtensionPackage; // the package can be different in your case
    
    ...
    public class MainApplication extends Application implements ReactApplication {
      ...
    
      @Override
      protected List<ReactPackage> getPackages() {
        List<ReactPackage> packages = new PackageList(this).getPackages();
    
        // Add this line.
        packages.add(new CustomDevMenuExtensionPackage());
    
        return packages;
      }
    }
  4. Create a Swift class which implements DevMenuExtensionProtocol for your extension.

    // CustomDevMenuExtension.swift
    import EXDevMenuInterface
    
    @objc(CustomDevMenuExtension)
    open class CustomDevMenuExtension: NSObject, RCTBridgeModule, DevMenuExtensionProtocol {
      public static func moduleName() -> String! {
        return "CustomDevMenuExtension" // here you can provide name for your extension
      }
    
      @objc
      open func devMenuItems() -> [DevMenuItem]? {
        // Firstly, create a function which will be called when the user presses the button.
        let clearNSUserDefaultsOnPress = {
          let prefs = UserDefaults.standard
          prefs.removeObject(forKey: "key_to_remove")
        }
    
        let clearNSUserDefaults = DevMenuAction(
          withId: "clear_nsusersdefaults", // This string identifies your custom action. Make sure that it's unique.
          action: clearNSUserDefaultsOnPress
        )
    
        clearNSUserDefaults.label = { "Clear NSUserDefaults" } // This string will be displayed in the dev menu.
        clearNSUserDefaults.glyphName = { "delete" } // This is a icon name used to present your action. You can use any icon from the `MaterialCommunityIcons`.
        clearNSUserDefaults.importance = DevMenuItem.ImportanceHigh // This value tells the dev-menu in which order the actions should be rendered.
        clearNSUserDefaults.registerKeyCommand(input: "p", modifiers: .command) // You can associate key commend with your action.
    
        // Return created object. Note: you can register multiple actions if you want.
        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.

  5. Create a .m file to integrate Swift class with react native and add following lines.

    // CustomDevMenuExtension.m
    
    #import <React/RCTBridgeModule.h>
    
    @interface RCT_EXTERN_REMAP_MODULE(CustomDevMenuExtensionObjc, CustomDevMenuExtension, NSObject)
    @end
  6. Add the following line into the bridging header.

    #import <React/RCTBridgeModule.h>

After all those steps you should see something like this:

Final result

📚 API

import * as DevMenu from 'expo-dev-menu';

For now, the DevMenu module exports only one method - openMenu.

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.

5.0.11

6 days ago

5.0.12

5 days ago

5.0.9

8 days ago

5.0.10

7 days ago

5.0.8

10 days ago

5.0.7

14 days ago

5.0.6

14 days ago

5.0.5

15 days ago

5.0.4

15 days ago

5.0.3

16 days ago

5.0.2

17 days ago

5.0.1

20 days ago

5.0.0

21 days ago

4.5.8

2 months ago

4.5.7

2 months ago

4.5.6

3 months ago

3.2.4

3 months ago

3.2.3

3 months ago

4.5.5

4 months ago

4.5.4

4 months ago

4.5.3

4 months ago

4.5.2

5 months ago

4.5.0

5 months ago

4.5.1

5 months ago

3.2.2

6 months ago

4.4.0

6 months ago

4.3.0

7 months ago

3.2.1

8 months ago

3.2.0

8 months ago

4.2.1

8 months ago

4.2.0

8 months ago

4.1.0

8 months ago

3.1.10

9 months ago

3.1.9

10 months ago

3.1.8

10 months ago

4.0.0

9 months ago

3.1.3

11 months ago

3.1.2

11 months ago

3.1.1

11 months ago

3.1.0

11 months ago

3.1.7

10 months ago

3.1.6

10 months ago

3.1.5

10 months ago

3.1.4

11 months ago

2.2.0

1 year ago

3.0.0

1 year ago

2.1.2

1 year ago

2.1.4

1 year ago

2.1.3

1 year ago

2.1.1

1 year ago

2.1.0

1 year ago

2.0.2

1 year ago

2.0.1

2 years ago

2.0.0

2 years ago

1.3.1

2 years ago

1.3.0

2 years ago

1.2.1

2 years ago

1.2.0

2 years ago

1.0.1

2 years ago

1.0.0

2 years ago

0.11.0

2 years ago

1.1.1

2 years ago

1.1.0

2 years ago

0.9.4

2 years ago

0.10.7

2 years ago

0.10.1

2 years ago

0.10.2

2 years ago

0.10.3

2 years ago

0.10.4

2 years ago

0.10.5

2 years ago

0.10.6

2 years ago

0.10.0

2 years ago

0.8.5

2 years ago

0.9.3

2 years ago

0.8.6

2 years ago

0.9.0

2 years ago

0.9.2

2 years ago

0.9.1

2 years ago

0.8.4

3 years ago

0.8.3

3 years ago

0.8.2

3 years ago

0.8.1

3 years ago

0.8.0

3 years ago

0.7.7

3 years ago

0.7.6

3 years ago

0.7.5

3 years ago

0.7.4

3 years ago

0.7.3

3 years ago

0.7.2

3 years ago

0.7.1

3 years ago

0.7.0

3 years ago

0.6.0

3 years ago

0.5.0

3 years ago

0.5.2

3 years ago

0.5.1

3 years ago

0.4.1

3 years ago

0.4.0

3 years ago

0.3.1

3 years ago

0.3.0

3 years ago

0.2.2

3 years ago

0.2.1

3 years ago

0.2.0

3 years ago

0.1.2

3 years ago

0.1.1

3 years ago

0.1.0

3 years ago

0.0.4

3 years ago

0.0.3

3 years ago

0.0.2

4 years ago

0.0.1

4 years ago

0.0.0

4 years ago