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

Why are the results of my bidding promotion getting worse and worse?

Question: I am the manager of online marketing fo...

Uninvited Guests: Alien Molecules in the Orbit of Near-Earth Asteroids

We know that in addition to the eight planets, th...

Summary of tips for Android code optimization

Preface This article mainly introduces some small...

How a designer wrote and released an app in 4 months

[[132950]] The story behind GAget, the Google Ana...

My 30 operating notes, you may need

01 Chat with a product manager . He said that it ...

iPhone 6: Driving Taiwan's continued economic growth

The launch of new technology products such as App...

How much does it cost to make a men’s clothing mini program in Fuyang?

There are two types of Fuyang Men's Wear WeCh...

How much does it cost to create a WeChat mini program?

There is never a shortage of entrepreneurs in the...

Is the Earth Getting Saltier? Human Activities Accelerate the Natural Salt Cycle

Science Times reporter Ma Aiping Professor Sujay ...

Where did the warning color of locusts come from?

Produced by: Science Popularization China Author:...