13

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.

0

2 Answers 2

34

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.

Sign up to request clarification or add additional context in comments.

7 Comments

note: you can rewrite MapBuilder.<String, Object>builder() .put("onClick", MapBuilder.of("registrationName", "onClick")) .build(); to MapBuilder.of( "onClick", MapBuilder.of("registrationName", "onClick"));
Hello, I cant get my head around this :( if I use your example with onClick event, it works, but when I rename onClick (everywhere) to for example onMyAction, it does nothing. Also, if i use onClick, I cant pass read any data from event property that are sent from Android :\ its just undefined. Do you have any additional resources to learn from? Is onClick reserved word and thats the reason why it works? Thanks
This example is less than ideal because the native event and the JS event have the exact same name (onClick) so it's not clear which of these magical strings refer to a JS event and which refers to a native event.
Thanks for the advice @AndrewKoster - this explanation took a fair amount of research and experimentation to get right, and it worked for my use case. I believe the community is in serious need of more information on these events; it would be great if you could put up an answer that fills in the blanks and shows the difference between the names of the native vs JS events. I still receive emails about this answer but am unable to help further.
Yeah, getting anything done in RN takes some research... the documentation is sparse and there isn't much example code. I'll see if I can clean up some of my code and post it.
|
21

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.

5 Comments

ok I have this inside my view class ' public void onReceiveNativeEvent() { WritableMap event = Arguments.createMap(); event.putString("message", "myValue"); mReactContext.getJSModule(RCTEventEmitter.class).receiveEvent(1,"MOJE",event); }' And how can I call this ?
I can get modul callback like this NativeModules.MyModule.getMyModuleVoid((varialble) =>{ console.log("my log : " + varialble) } ); I need something like that
Thanks for the nicely described answer! At last, I found why my custom events do nothing =)
@gre this post was extremely useful in pointing me in the right direction! I still struggled to track down some missing details, which I've tried to fill in comprehensively in another answer.
@gre I tried to recreate your answer in RN 0.62, but it does not work. I have posted a question on stack overflow here stackoverflow.com/questions/63753720/… can anyone here help me out. I even tried using the topChange example given int the docs, but that does not work either.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.