Skip to content

Commit d0596d4

Browse files
Add Java Turbo Module Event Emitter example (#44906)
Summary: Pull Request resolved: #44906 Shows a proof of concept how '*strongly typed Turbo Module scoped*' `EventEmitters` can be used in a Java Turbo Module. ## Changelog: [Android] [Added] - Add Java Turbo Module Event Emitter example Reviewed By: javache Differential Revision: D57530807
1 parent d8739e1 commit d0596d4

File tree

11 files changed

+126
-9
lines changed

11 files changed

+126
-9
lines changed

packages/react-native-codegen/src/parsers/error-utils.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,17 +164,15 @@ function throwIfEventEmitterTypeIsUnsupported(
164164
parser: Parser,
165165
nullable: boolean,
166166
untyped: boolean,
167-
cxxOnly: boolean,
168167
) {
169-
if (nullable || untyped || !cxxOnly) {
168+
if (nullable || untyped) {
170169
throw new UnsupportedModuleEventEmitterPropertyParserError(
171170
nativeModuleName,
172171
propertyName,
173172
propertyValueType,
174173
parser.language(),
175174
nullable,
176175
untyped,
177-
cxxOnly,
178176
);
179177
}
180178
}

packages/react-native-codegen/src/parsers/errors.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,12 @@ class UnsupportedModuleEventEmitterPropertyParserError extends ParserError {
100100
language: ParserType,
101101
nullable: boolean,
102102
untyped: boolean,
103-
cxxOnly: boolean,
104103
) {
105104
let message = `${language} interfaces extending TurboModule must only contain 'FunctionTypeAnnotation's or non nullable 'EventEmitter's. Further the EventEmitter property `;
106105
if (nullable) {
107106
message += `'${propertyValue}' must non nullable.`;
108107
} else if (untyped) {
109108
message += `'${propertyValue}' must have a concrete or void eventType.`;
110-
} else if (cxxOnly) {
111-
message += `'${propertyValue}' is only supported in C++ Turbo Modules.`;
112109
}
113110
super(nativeModuleName, propertyValue, message);
114111
}

packages/react-native-codegen/src/parsers/parsers-commons.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,6 @@ function buildEventEmitterSchema(
506506
parser,
507507
typeAnnotationNullable,
508508
typeAnnotationUntyped,
509-
cxxOnly,
510509
);
511510
const eventTypeResolutionStatus = resolveTypeAnnotationFN(
512511
typeAnnotation.typeParameters.params[0],

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.facebook.common.logging.FLog;
1414
import com.facebook.infer.annotation.Assertions;
1515
import com.facebook.infer.annotation.ThreadConfined;
16+
import com.facebook.proguard.annotations.DoNotStrip;
1617
import com.facebook.react.common.ReactConstants;
1718
import com.facebook.react.common.annotations.DeprecatedInNewArchitecture;
1819
import com.facebook.react.common.annotations.StableReactNativeAPI;
@@ -54,6 +55,8 @@ public abstract class BaseJavaModule implements NativeModule {
5455
public static final String METHOD_TYPE_PROMISE = "promise";
5556
public static final String METHOD_TYPE_SYNC = "sync";
5657

58+
@Nullable protected CxxCallbackImpl mEventEmitterCallback;
59+
5760
private final @Nullable ReactApplicationContext mReactApplicationContext;
5861

5962
public BaseJavaModule() {
@@ -129,4 +132,9 @@ protected final ReactApplicationContext getReactApplicationContext() {
129132
}
130133
return null;
131134
}
135+
136+
@DoNotStrip
137+
private final void setEventEmitterCallback(CxxCallbackImpl eventEmitterCallback) {
138+
mEventEmitterCallback = eventEmitterCallback;
139+
}
132140
}

packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -992,4 +992,34 @@ jsi::Value JavaTurboModule::invokeJavaMethod(
992992
}
993993
}
994994

995+
void JavaTurboModule::setEventEmitterCallback(
996+
jni::alias_ref<jobject> jinstance) {
997+
JNIEnv* env = jni::Environment::current();
998+
auto instance = jinstance.get();
999+
static jmethodID cachedMethodId = nullptr;
1000+
if (cachedMethodId == nullptr) {
1001+
jclass cls = env->GetObjectClass(instance);
1002+
cachedMethodId = env->GetMethodID(
1003+
cls,
1004+
"setEventEmitterCallback",
1005+
"(Lcom/facebook/react/bridge/CxxCallbackImpl;)V");
1006+
}
1007+
1008+
auto eventEmitterLookup =
1009+
[&](const std::string& eventName) -> AsyncEventEmitter<folly::dynamic>& {
1010+
return static_cast<AsyncEventEmitter<folly::dynamic>&>(
1011+
*eventEmitterMap_[eventName].get());
1012+
};
1013+
1014+
jvalue arg;
1015+
arg.l = JCxxCallbackImpl::newObjectCxxArgs([eventEmitterLookup = std::move(
1016+
eventEmitterLookup)](
1017+
folly::dynamic args) {
1018+
auto eventName = args.at(0).asString();
1019+
auto eventArgs = args.size() > 1 ? args.at(1) : nullptr;
1020+
eventEmitterLookup(eventName).emit(std::move(eventArgs));
1021+
}).release();
1022+
env->CallVoidMethod(instance, cachedMethodId, arg);
1023+
}
1024+
9951025
} // namespace facebook::react

packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ class JSI_EXPORT JavaTurboModule : public TurboModule {
5151
size_t argCount,
5252
jmethodID& cachedMethodID);
5353

54+
void setEventEmitterCallback(jni::alias_ref<jobject> instance);
55+
5456
private:
5557
// instance_ can be of type JTurboModule, or JNativeModule
5658
jni::global_ref<jobject> instance_;

packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/NativeSampleTurboModuleSpec.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,22 @@ public NativeSampleTurboModuleSpec(ReactApplicationContext reactContext) {
3535
super(reactContext);
3636
}
3737

38+
protected final void emitOnPress() {
39+
mEventEmitterCallback.invoke("onPress");
40+
}
41+
42+
protected final void emitOnClick(String value) {
43+
mEventEmitterCallback.invoke("onClick", value);
44+
}
45+
46+
protected final void emitOnChange(ReadableMap value) {
47+
mEventEmitterCallback.invoke("onChange", value);
48+
}
49+
50+
protected void emitOnSubmit(ReadableArray value) {
51+
mEventEmitterCallback.invoke("onSubmit", value);
52+
}
53+
3854
@Override
3955
public @Nonnull String getName() {
4056
return NAME;

packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/ReactCommon/SampleTurboModuleSpec.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,15 @@ NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(
351351
1, __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectAssert};
352352
methodMap_["promiseAssert"] = MethodMetadata{
353353
0, __hostFunction_NativeSampleTurboModuleSpecJSI_promiseAssert};
354+
eventEmitterMap_["onPress"] =
355+
std::make_shared<AsyncEventEmitter<folly::dynamic>>();
356+
eventEmitterMap_["onClick"] =
357+
std::make_shared<AsyncEventEmitter<folly::dynamic>>();
358+
eventEmitterMap_["onChange"] =
359+
std::make_shared<AsyncEventEmitter<folly::dynamic>>();
360+
eventEmitterMap_["onSubmit"] =
361+
std::make_shared<AsyncEventEmitter<folly::dynamic>>();
362+
setEventEmitterCallback(params.instance);
354363
}
355364

356365
std::shared_ptr<TurboModule> SampleTurboModuleSpec_ModuleProvider(

packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleTurboModule.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,26 @@ public double getRootTag(double arg) {
104104
@Override
105105
public void voidFunc() {
106106
log("voidFunc", "<void>", "<void>");
107-
return;
107+
emitOnPress();
108+
emitOnClick("click");
109+
{
110+
WritableNativeMap map = new WritableNativeMap();
111+
map.putInt("a", 1);
112+
map.putString("b", "two");
113+
emitOnChange(map);
114+
}
115+
{
116+
WritableNativeArray array = new WritableNativeArray();
117+
WritableNativeMap map = new WritableNativeMap();
118+
map.putInt("a", 1);
119+
map.putString("b", "two");
120+
array.pushMap(map);
121+
WritableNativeMap map1 = new WritableNativeMap();
122+
map1.putInt("a", 3);
123+
map1.putString("b", "four");
124+
array.pushMap(map1);
125+
emitOnSubmit(array);
126+
}
108127
}
109128

110129
// This function returns {@link WritableMap} instead of {@link Map} for backward compat with

packages/react-native/src/private/specs/modules/NativeSampleTurboModule.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import type {
1212
RootTag,
1313
TurboModule,
1414
} from '../../../../Libraries/TurboModule/RCTExport';
15-
import type {UnsafeObject} from '../../../../Libraries/Types/CodegenTypes';
15+
import type {
16+
EventEmitter,
17+
UnsafeObject,
18+
} from '../../../../Libraries/Types/CodegenTypes';
1619

1720
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
1821

@@ -21,7 +24,17 @@ export enum EnumInt {
2124
B = 42,
2225
}
2326

27+
export type ObjectStruct = {
28+
a: number,
29+
b: string,
30+
c?: ?string,
31+
};
32+
2433
export interface Spec extends TurboModule {
34+
+onPress: EventEmitter<void>;
35+
+onClick: EventEmitter<string>;
36+
+onChange: EventEmitter<ObjectStruct>;
37+
+onSubmit: EventEmitter<ObjectStruct[]>;
2538
// Exported methods.
2639
+getConstants: () => {|
2740
const1: boolean,

packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010

1111
import type {RootTag} from 'react-native/Libraries/ReactNative/RootTag';
12+
import type {EventSubscription} from 'react-native/Libraries/vendor/emitter/EventEmitter';
1213

1314
import styles from './TurboModuleExampleCommon';
1415
import * as React from 'react';
@@ -68,6 +69,7 @@ type ErrorExamples =
6869

6970
class SampleTurboModuleExample extends React.Component<{||}, State> {
7071
static contextType: React$Context<RootTag> = RootTagContext;
72+
eventSubscriptions: EventSubscription[] = [];
7173

7274
state: State = {
7375
testResults: {},
@@ -218,6 +220,30 @@ class SampleTurboModuleExample extends React.Component<{||}, State> {
218220
'The JSI bindings for SampleTurboModule are not installed.',
219221
);
220222
}
223+
this.eventSubscriptions.push(
224+
NativeSampleTurboModule.onPress(value => console.log('onPress: ()')),
225+
);
226+
this.eventSubscriptions.push(
227+
NativeSampleTurboModule.onClick(value =>
228+
console.log(`onClick: (${value})`),
229+
),
230+
);
231+
this.eventSubscriptions.push(
232+
NativeSampleTurboModule.onChange(value =>
233+
console.log(`onChange: (${JSON.stringify(value)})`),
234+
),
235+
);
236+
this.eventSubscriptions.push(
237+
NativeSampleTurboModule.onSubmit(value =>
238+
console.log(`onSubmit: (${JSON.stringify(value)})`),
239+
),
240+
);
241+
}
242+
243+
componentWillUnmount() {
244+
for (const subscription of this.eventSubscriptions) {
245+
subscription.remove();
246+
}
221247
}
222248

223249
render(): React.Node {

0 commit comments

Comments
 (0)