[JAVA] Bridge commentary on React Native Android

function

Native Modules Take Module, which displays a dialog, as an example.

[function] 1, Display the Android dialog. (Creation of function) 2, When the "OK" button in the dialog is clicked, "true" is displayed on the console. (Event notification) 3, When the "DATE" button is clicked, the current time is displayed. (Callback)

DialogModule.java


public class DialogModule extends ReactContextBaseJavaModule {
    //Constructor
    public DialogModule(@NonNull ReactApplicationContext reactContext) {
        super(reactContext);
    }
   //Something like a tag to call this module
   @Override
    public String getName() {
        return "DialogModule";
    }

    //You can define class constants that can be used in RN
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();
        constants.put("SINGLE_BUTTON", "SINGLE");
        constants.put("DOUBLE_BUTTON", "DOUBLE");
        return constants;
    }

    //Function to display a dialog
    @ReactMethod
    public void showDialog(String message, String type) {
        if (type.equals("SINGLE")) {
            new AlertDialog.Builder(getCurrentActivity())
                    .setTitle("DialogModule")
                    .setMessage(message)
                    .setPositiveButton("CLOSE", (dialog, which) -> {
                        dialog.dismiss();
                    })
                    .show();
        } else {
            new AlertDialog.Builder(getCurrentActivity())
                    .setTitle("DialogModule")
                    .setMessage(message)
                    .setPositiveButton("OK", (dialog, which) -> {
                        sendEvent();
                    })
                    .setNegativeButton("CLOSE", (dialog, which) -> {
                        dialog.dismiss();
                    })
                    .show();
        }
    }

    //A function that displays the current time and calls back
    @ReactMethod
    private void getCurrentTime(Callback callback) {
        Calendar calendar = Calendar.getInstance();
        callback.invoke(calendar.getTime().toString());
    }

    //An event that notifies you that a button has been clicked
    private void sendEvent() {
        WritableMap params = Arguments.createMap();
        params.putBoolean("click", true);
        getReactApplicationContext()
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit("onClick", params);
    }
}

Commentary

getName It is necessary to describe the character string when calling from RN like "DialogModule".

@Override
public String getName() {
    return "DialogModule";
}

getConstants You can set Molue class constants that can be used from the RN.

public Map<String, Object> getConstants() {
    final Map<String, Object> constants = new HashMap<>();
    constants.put("SINGLE_BUTTON", "SINGLE");
    constants.put("DOUBLE_BUTTON", "DOUBLE");
    return constants;
}

@ReactMethod You can set the methods available from the RN. If you want to execute a callback, execute it with .invoke ().

@ReactMethod
private void getCurrentTime(Callback callback) {
    Calendar calendar = Calendar.getInstance();
    callback.invoke(calendar.getTime().toString());
}

Event (listener) You can set the events that can be registered in RN.

//Set the callback value
WritableMap params = Arguments.createMap();
params.putBoolean("click", true);
//Set event name and callback value
getReactApplicationContext()
    .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
    .emit("onClick", params);

Native UI Components Take a View that displays a video player as an example.

[function] 1, Display the video player. (Create View) 2, Receive the url from the RN side with props and play the video. 3, Display the log at the end of playback. (View event notification)

VideoViewManager.java


public class VideoViewManager extends SimpleViewManager<VideoView> {
    private Context context;

    //Something like a tag to call this module
    @Override
    public String getName() {
        return "VideoView";
    }

    //Something like a constructor that returns an instant of the View to use
    @Override
    protected VideoView createViewInstance(ThemedReactContext reactContext) {
        this.context = reactContext;
        return new VideoView(reactContext);
    }

    //Function that processes with the value received by Props
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
    @ReactProp(name="url")
    public void setVideoPath(VideoView videoView, String urlPath) {
        Uri uri = Uri.parse(urlPath);
        videoView.setMediaController(new MediaController(context));
        videoView.setVideoURI(uri);
        //Play as soon as it is ready to play
        videoView.setOnPreparedListener(mp -> {
            videoView.start();
        });
        //Call back to "onFinish" of props at the end of playback.(notification)
        videoView.setOnCompletionListener(mp -> {
            ReactContext reactContext = (ReactContext)context;
            
            WritableMap event = Arguments.createMap();
            event.putString("message", "onDirectEvent");
            reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onDirectEvent",event);

            WritableMap event2 = Arguments.createMap();
            event2.putString("message", "onBubblingEvent");
            reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onBubblingEvent",event2);
        });
        videoView.getDuration();
    }

    @Override
    public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
        return MapBuilder.<String, Object>builder()
                .put("onDirectEvent", MapBuilder.of("registrationName", "onDirectEvent"))
                .build();
    }

    @Override
    public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
        return MapBuilder.<String, Object>builder()
                .put("onBubblingEvent", MapBuilder.of("phasedRegistrationNames",
                                    MapBuilder.of(
                                            "bubbled", "onBubble",
                                            "captured", "onCapture")))
                .build();
    }
}

Commentary

getName The character string when calling from RN must be described as "VideoView".

@Override
public String getName() {
    return "VideoView";
}

@ReactProp Setter method that receives Props of View The method name itself can be anything @ReactProp (name =" props name ") public void setProp (View type view, value of type Props)

@ReactProp(name="url")
public void setVideoPath(VideoView videoView, String urlPath) {
        // ...
}

createViewInstance A function that returns an instance of View

@Override
protected VideoView createViewInstance(ThemedReactContext reactContext) {
    return new VideoView(reactContext);
}

Event notification You can set Props callbacks.

Event registration

There are two methods for registering events. ・ GetExportedCustomDirectEventTypeConstants ・ GetExportedCustomBubblingEventTypeConstants

getExportedCustomDirectEventTypeConstants Notify one props with one event. How to register MapBuilder.builder (). Put ("event name", MapBuilder.of ("registrationName", "props name")). build ();

getExportedCustomBubblingEventTypeConstants Two props can be notified in one event. How to register MapBuilder.builder (). Put ("event name", MapBuilder.of ("phasedRegistrationNames", MapBuilder.of ("bubbled", "props name 1", "captured", "props name 2"))). build ();

//Function to register an event 1
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
    return MapBuilder.<String, Object>builder()
            .put("onDirectEvent", MapBuilder.of("registrationName", "onDirectEvent"))
            .build();
}

//Function 2 to register an event
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
    return MapBuilder.<String, Object>builder()
            .put("onBubblingEvent", MapBuilder.of("phasedRegistrationNames",
                                MapBuilder.of(
                                        "bubbled", "onBubble",
                                        "captured", "onCapture")))
            .build();
}

Call receiveEvent where you want to be notified receiveEvent ("view ID", "event name", callback value)

// ...
ReactContext reactContext = (ReactContext)context;
//Process to notify event 1"onDirectEvent"Event
WritableMap event = Arguments.createMap();
event.putString("message", "onDirectEvent");
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onDirectEvent",event);

//Process to notify event 2"onBubblingEvent"Event
WritableMap event2 = Arguments.createMap();
event2.putString("message", "onBubblingEvent");
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onBubblingEvent",event2);
// ...

On the RN side, the event is notified as follows

<VideoView
      onDirectEvent={({ nativeEvent }) => console.log(nativeEvent.message)} // onDirectEvent
      onCapture={({ nativeEvent }) => console.log(nativeEvent.message)} // onBubblingEvent
      onBubble={({ nativeEvent }) => console.log(nativeEvent.message)} // onBubblingEvent
/>

Module and UI Component registration

Register the created Module and UI Component as follows

ExamplePackage.java


public class ExamplePackage implements ReactPackage {
    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.<ViewManager>singletonList(
                //Add as UI Components increase
                new VideoViewManager()
        );
    }

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext
            return Collections.<ViewManager>singletonList(
                //Add as more Modules
                new DialogModule(reactContext)
        );
    }
}

Register the created package in getPackages in MainApplication.java

MainApplication.java


@Override
protected List<ReactPackage> getPackages() {
      List<ReactPackage> packages = new PackageList(this).getPackages();
           //Add as the number of packages increases
           packages.add(new ExamplePackage());
      return packages;
}

RN side Native Modules

Dialog.jsx


import React from 'react';
import { NativeModules } from 'react-native';
//Return value of getName function("DialogModule")Specify
DialogModule = NativeModules.DialogModule;

const Dialog = () => {
  const [date, setDate] = React.useState("");

  React.useEffect(() => {
    const eventEmitter = new NativeEventEmitter(DialogModule);
    eventEmitter.addListener('onClick', (event) => {
       console.log(event.change) // "true"
    });
  }, [])

  return (
    <>
      <TouchableOpacity onPress={() => DialogModule.showDialog('SINGLE BUTTON', DialogModule.SINGLE_BUTTON)}>
        <Text>SINGLE BUTTON Dialog</Text>
      </TouchableOpacity>
      <TouchableOpacity onPress={() => DialogModule.showDialog('DOUBLE BUTTON', DialogModule.DOUBLE_BUTTON)}>
        <Text>DOUBLE BUTTON Dialog</Text>
      </TouchableOpacity>
      <TouchableOpacity onPress={() => DialogModule.getCurrentTime(time => setDate(time))}>
        <Text>DATE</Text>
      </TouchableOpacity>
        <Text>CURRENT DATE:</Text>
      <Text>[ {date} ]</Text>
    </>
  )
}

export default Dialog

Native UI Components

VideoView.jsx


import React from 'react';
import { requireNativeComponent } from 'react-native';
VideoView = requireNativeComponent('VideoView');

const VideoView = () => {

  return (
    <>
      <VideoView 
        style={{ width: '100%', height: '100%' }}
        url="https://www.radiantmediaplayer.com/media/bbb-360p.mp4"
        onDirectEvent={({ nativeEvent }) => console.log(nativeEvent.message)}
        onCapture={({ nativeEvent }) => console.log(nativeEvent.message)}
        onBubble={({ nativeEvent }) => console.log(nativeEvent.message)} />
    </>
  )
}

export default VideoView

bonus

When you want to call the function implemented in UI Component from Module

ExamplePackage.java


public class ExamplePackage implements ReactPackage {
    private ExampleViewManager instance;
    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.<ViewManager>singletonList(
                instance
        );
    }

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext
            instance = new ExampleViewManager();
            return Collections.<ViewManager>singletonList(
                //Pass an instance of ViewManager to the module
                //After that, you can call the method of the instance from the function defined in the module.
                new ExampleModule(reactContext, instance)
        );
    }
}

Finally

Regarding the bridge part of Android, I had a lot of trouble with only English articles and many sample code that did not work. Since I didn't have much time to write an article, there may be some omissions or mistakes, so please use the content of the article as a reference.

Next, the article on the 20th day of React Native Advent Calendar is @ duka's "A story about AB test": tada:

Recommended Posts

Bridge commentary on React Native Android
How to write React Native bridge ~ Android version ~
Use native code on Android
Using Native UI Component with React Native (Android version)
[Android] Notes on xml
Android environment construction that even monkeys can understand [React Native]
Use Java included with Android Studio to build React Native
Customize list view on Android
Multipart transmission library on Android
Use serial communication on Android
ROS app development on Android
Commentary on partial! --Ruby on Rails