Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating Custom UI component for android on React Native. How to send data to JS?

I create UI. It works well. But i don't know how to send data from Java to JS? In react native moduls I can use callback and activete this onClick events. But in UI i don't know.

More about what I need. I have android component. Send it to JS this way createViewInstance(ThemedReactContext reactContext)

and users something change inside component. And these changes I see in java class. I need to send these changes to the JS when JS ask for them.

You know how to send data from UI component to JS? Please give me some example. Thank you.

like image 992
Lukáš Šálek Avatar asked Jan 12 '16 09:01

Lukáš Šálek


2 Answers

Building on gre's answer that put me on the right track, but still left me with a lot of work to do, I'll try to explain some of the missing details.

There are 2 basic ways to do this:

  1. use an existing event type
  2. create & use a custom event type

Existing Events

As gre mentioned, the React Native docs explain this in the Native UI Components section of Events.

They show how to send the event using the following code:

  WritableMap event = Arguments.createMap();
  event.putString("message", "MyMessage");
  ReactContext reactContext = (ReactContext)getContext();
  reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
      getId(), "topChange", event);

With this explanation:

The event name topChange maps to the onChange callback prop in JavaScript (mappings are in UIManagerModuleConstants.java).

The actual definition from UIManagerModuleConstants.java looks like this:

"topChange",
    MapBuilder.of(
        "phasedRegistrationNames",
            MapBuilder.of(
                "bubbled", "onChange", 
                "captured", "onChangeCapture")))

i.e. by using the event topChange in the Android code, you can intercept it in JS with either onChange or onChangeCapture due to this mapping.

You can find many other existing events declared in there to piggy-back on.

There are also "direct" events declared in that may be more useful:

"topLayout", 
    MapBuilder.of("registrationName", "onLayout")

i.e. Android event topLayout maps to JS event callback onLayout

(I do not understand the difference between the "bubbled" vs "captured" vs "direct" event types)

To receive the event in JS, take note of the 3 places _onChange() is referenced in the docs:

  1. create the callback method: _onChange(event: Event) {}

  2. bind it in the constructor: this._onChange = this._onChange.bind(this);

  3. pass it when creating your custom view: return <RCTMyCustomView {...this.props} onChange={this._onChange} />;


Custom Events

Custom events need to be declared to the system before they can be used, mapping Android events to JS events in a similar way to how they are done by React Native above.

This is done by overriding one of the following methods in your ViewManager:

  • getExportedCustomBubblingEventTypeConstants()
  • getExportedCustomDirectEventTypeConstants()

The javadoc is very helpful in showing how the mapping should work, but I found it useful to reference the React Native code in UIManagerModuleConstants mentioned above.

Once it is declared there, you use it as you would any other "existing" event.


Example Custom Event implementation

I wanted to send the click event from Android up to JS, and to call it onClick. I chose to use a "direct" event for this. I also chose to use the same name in Android and in JS - this is not necessary.

3 files need to be modified:

ViewManager class

This code maps the Android event name "onClick" to the JS function "onClick".

/**
 * This method maps the sending of the "onClick" event to the JS "onClick" function.
 */
@Nullable @Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
    return MapBuilder.<String, Object>builder()
            .put("onClick",
                    MapBuilder.of("registrationName", "onClick"))
            .build();
}

View class

This code sends an event to the JS when the view is clicked. The name used here is the Android event name, which will map to whatever you set above in the ViewManager class.

    // trigger the onPress JS callback
    super.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            final Context context = getContext();
            if (context instanceof ReactContext) {
                ((ReactContext) context).getJSModule(RCTEventEmitter.class)
                        .receiveEvent(getId(),
                                "onClick", null);
            }
        }
    });

The instanceof check is there because this view is sometimes referenced from native code, outside the React context.

React Native JS component

Bind in constructor:

  constructor(props) {
    super(props);
    this.onClick = this.onClick.bind(this);
  }

Declare actual callback function:

  onClick(event: Event) {
    // do something
  }

Make sure to set the callback when rendering your view:

  render() {
    return <NativeView onClick={this.onClick} />;
  }

All very simple when laid out like this, but the documentation of the finer details is scattered around the web.

like image 155
Richard Le Mesurier Avatar answered Sep 29 '22 10:09

Richard Le Mesurier


http://facebook.github.io/react-native/docs/native-components-android.html#events

^this shows how you can trigger events from Java side to JS on an UI component.

but for "custom events" (events that are not pre-defined likes onLoad, onScroll, etc..) you will also need to override getExportedCustomDirectEventTypeConstants.

Here is an example, triggering onGLProgress for gl-react-native:

(1) define the custom event mapping: https://github.com/ProjectSeptemberInc/gl-react-native/blob/7d6e83de5a8280d06d47234fe756aa3050e9b9a1/android/src/main/java/com/projectseptember/RNGL/GLCanvasManager.java#L115-L116

(2) dispatch the event from Java to JS: https://github.com/ProjectSeptemberInc/gl-react-native/blob/0f64a63fec2281e9d6d3641b9061b771a44fcac8/android/src/main/java/com/projectseptember/RNGL/GLCanvas.java#L839-L849

(3) and on the JS side, you can give a onGLProgress prop callback.

like image 20
gre Avatar answered Sep 29 '22 09:09

gre