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

A deep dive into iOS 12's new features: A guide to Shortcuts

Apple introduced an exciting new feature called &...

Siri shortcuts and other features on iOS will also be moved to macOS

[[263433]] With the approach of macOS 10.15 and i...

[World Spine Day] Lower back pain, waist relief exercises can help

October 16th of every year is World Spine Day bec...

In the age of information overload, what else can content marketing do?

This is an age of information overload. Consumers...

Huawei P40Pro vs OnePlus 8Pro camera comparison: the gap is still huge

The recently released OnePlus 8Pro has gained a g...

Will there be a space version of ChatGPT?

ChatGPT is very popular now. As an artificial int...