Android native modules in React Native

Android native modules in React Native

[[176954]]

When developing Android apps with React Native, you may need to use modules that are not encapsulated by React Native. But you can write native modules in Java and then selectively expose public interfaces to React Native. Let's try it together!

What are we going to write?

At the time of writing this article, React Native includes an ImagePickerIOS component, but there is no corresponding ImagePicker component on the Android platform. We will now build a simple ImagePicker for Android that is roughly similar to ImagePickerIOS.

Writing an Android native module for React Native requires the following steps:

  1. Create a ReactPackage, include multiple modules (Native and Javascript) together, and then reference it in the getPackages method in MainActivity.
  2. Create a Java class that inherits ReactContextBaseJavaModule and implements the required interfaces, then register it to our ReactPackage.
  3. Override the getName method of the above class, which will be used as the calling method name of Javascript.
  4. Use @ReactMethod annotation to expose required public methods to JavaScript.
  5. ***, import your modules via NativeModules in Javascript.

Let’s practice this together.

Create a ReactPackage

Launch AndroidStudio and navigate to MyApp/android/app/src/main/java/com/myapp/MainActivity.java. It should look like this:

  1. package com.myapp;
  2.  
  3. import com.facebook.react.ReactActivity;
  4. import com.facebook.react.ReactPackage;
  5. import com.facebook.react.shell.MainReactPackage;
  6.  
  7. import java.util.Arrays;
  8. import java.util.List;
  9.  
  10. public class MainActivity extends ReactActivity {
  11.  
  12. @Override
  13. protected String getMainComponentName() {
  14. return   "MyApp" ;
  15. }
  16.  
  17. @Override
  18. protected boolean getUseDeveloperSupport() {
  19. return BuildConfig.DEBUG;
  20. }
  21.  
  22. @Override
  23. protected List<ReactPackage> getPackages() {
  24. return Arrays.<ReactPackage>asList(
  25. new MainReactPackage()
  26. );
  27. }
  28. }

Let's first import a package that has not yet been defined:

  1. import com.myapp.imagepicker.*; // import the package
  2.  
  3. public class MainActivity extends ReactActivity {
  4. @Override
  5. protected List<ReactPackage> getPackages() {
  6. return Arrays.<ReactPackage>asList(
  7. new MainReactPackage(),
  8. new ImagePickerPackage() // include it in getPackages
  9. );
  10. }
  11. }

Now let's write that package. We'll create a new directory for it called imagepicker and write ImagePickerPackage:

  1. package com.myapp.imagepicker;
  2.  
  3. import com.facebook.react.ReactPackage;
  4. import com.facebook.react.bridge.JavaScriptModule;
  5. import com.facebook.react.bridge.NativeModule;
  6. import com.facebook.react.bridge.ReactApplicationContext;
  7. import com.facebook.react.uimanager.ViewManager;
  8.  
  9. import java.util.ArrayList;
  10. import java.util.Collections;
  11. import java.util.List;
  12.  
  13. public class ImagePickerPackage implements ReactPackage {
  14. @Override
  15. public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
  16. List<NativeModule> modules = new ArrayList<>();
  17.  
  18. modules.add (new ImagePickerModule(reactContext));
  19.  
  20. return modules;
  21. }
  22.  
  23. @Override
  24. public List<Class<? extends JavaScriptModule>> createJSModules() {
  25. return Collections.emptyList();
  26. }
  27.  
  28. @Override
  29. public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
  30. return Collections.emptyList();
  31. }
  32. }

Now we have created a package and included it in MainActivity.

Create a ReactContextBaseJavaModule

We will start by creating ImagePickerModule, which extends ReactContextBaseJavaModule.

  1. package com.myapp.imagepicker;
  2.  
  3. import com.facebook.react.bridge.ReactContextBaseJavaModule;
  4.  
  5. public class ImagePickerModule extends ReactContextBaseJavaModule {
  6. public ImagePickerModule(ReactApplicationContext reactContext) {
  7. super(reactContext);
  8. }
  9. }

This is a good start, in order for React Native to find our module from NativeModules, we need to override the getName method.

  1. @Override
  2. public String getName() {
  3. return   "ImagePicker" ;
  4. }

Now that we have a native module that can be imported by JavaScript code, let's make it do something interesting.

Exposure method

ImagePickerIOS defines an openSelectDialog method that can be passed a configuration object and callbacks for failure and success. Let's define a similar method in ImagePickerModule.

  1. import com.facebook.react.bridge.Callback;
  2. import com.facebook.react.bridge.ReadableMap;
  3.  
  4. public class ImagePickerModule extends ReactContextBaseJavaModule {
  5. @ReactMethod
  6. public void openSelectDialog(ReadableMap config, Callback successCallback, Callback cancelCallback) {
  7. Activity currentActivity = getCurrentActivity();
  8.      
  9. if (currentActivity == null ) {
  10. cancelCallback.invoke( "Activity doesn't exist" );
  11. return ;
  12. }
  13. }
  14. }

Here we import Callback and ReadableMap from React Native to correspond to function and object in JavaScript. We annotate this method with @ReactMethod so that it can be referenced by JavaScript as part of ImagePicker.

In the method body we get the current activity, and if there is no activity, we call the cancel callback method. We now have a functioning method, but it doesn't do anything interesting yet. Let's use it to open the photo album.

  1. public class ImagePickerModule extends ReactContextBaseJavaModule {
  2. private static final int PICK_IMAGE = 1;
  3.  
  4. private Callback pickerSuccessCallback;
  5. private Callback pickerCancelCallback;
  6.  
  7. @ReactMethod
  8. public void openSelectDialog(ReadableMap config, Callback successCallback, Callback cancelCallback) {
  9. Activity currentActivity = getCurrentActivity();
  10.  
  11. if (currentActivity == null ) {
  12. cancelCallback.invoke( "Activity doesn't exist" );
  13. return ;
  14. }
  15.  
  16. pickerSuccessCallback = successCallback;
  17. pickerCancelCallback = cancelCallback;
  18.  
  19. try {
  20. final Intent galleryIntent = new Intent();
  21.  
  22. galleryIntent.setType( "image/*" );
  23. galleryIntent.setAction(Intent.ACTION_GET_CONTENT);
  24.  
  25. final Intent chooserIntent = Intent.createChooser(galleryIntent, "Pick an image" );
  26.  
  27. currentActivity.startActivityForResult(chooserIntent, PICK_IMAGE);
  28. } catch (Exception e) {
  29. cancelCallback.invoke(e);
  30. }
  31. }
  32. }

First, we set up the callback, then we create an Intent and pass it to startActivityForResult. Finally, we wrap everything in a try/catch block to handle any exceptions that might occur.

When you call openSelectDialog, you should be able to see an album. However, when you select a picture, the album does not do anything. In order to be able to process the image data, we need to handle the return value of the activity in the module.

First, we need to add the activity event listener to the react context:

  1. public class ImagePickerModule extends ReactContextBaseJavaModule implements ActivityEventListener {
  2. public ImagePickerModule(ReactApplicationContext reactContext) {
  3. super(reactContext);
  4. reactContext.addActivityEventListener(this);
  5. }
  6. }

Now we can get the data returned by the album.

  1. @Override
  2. public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
  3. if (pickerSuccessCallback != null ) {
  4. if (resultCode == Activity.RESULT_CANCELED) {
  5. pickerCancelCallback.invoke( "ImagePicker was canceled" );
  6. } else if (resultCode == Activity.RESULT_OK) {
  7. Uri uri = intent.getData();
  8.  
  9. if (uri == null ) {
  10. pickerCancelCallback.invoke( "No image data found" );
  11. } else {
  12. try {
  13. pickerSuccessCallback.invoke(uri);
  14. } catch (Exception e) {
  15. pickerCancelCallback.invoke( "No image data found" );
  16. }
  17. }
  18. }
  19. }
  20. }

Here we should be able to get the image URI through the success callback.

  1. NativeModules.ImagePicker.openSelectDialog(
  2. {}, // no config yet
  3. (uri) => { console.log(uri) },
  4. (error) => { console.log(error) }
  5. )

To roughly mimic the behavior of ImagePickerIOS, we can allow the user to select an image, video, or open the camera directly. The writing of these functions is basically the same as above, and we will leave it as an exercise for the reader.

<<:  Contract Programming vs Defensive Programming

>>:  Android Development Universal Rounded Corner ImageView

Recommend

How can online and offline APPs complement each other’s strengths?

The specific strategies for enterprises to implem...

Xiaohongshu Brand Upgrade Methodology

In the era of universal IP, many brands are tryin...

WeChat Moments advertising creation process and advertising cases!

01 What is WeChat Moments Advertising? WeChat Mom...

What are the page ranking factors related to keywords?

The page ranking factors related to keywords are ...

Open the door quickly, the drone is here to deliver fresh seafood!

Nowadays, driven by the strategy of building a st...

Li Xingxing: Practical Editing Training Camp

Li Xingxing: Editing Practice Training Camp Resou...

How to build a traffic circulation system

Before I start sharing how to build a traffic cir...