Skip to content

Commit ad3df84

Browse files
Add EventEmitter Code-gen support for Java and ObjC Turbo Modules (#45119)
Summary: Pull Request resolved: #45119 ## Changelog: [iOS][Added] - Add EventEmitter Code-gen support for Java and ObjC Turbo Modules Reviewed By: RSNara Differential Revision: D58929417 fbshipit-source-id: 5208ba5ecb5882d47c3827c2aa8e3a54a3d7f2b6
1 parent 9d30523 commit ad3df84

File tree

14 files changed

+1305
-47
lines changed

14 files changed

+1305
-47
lines changed

packages/react-native-codegen/e2e/__tests__/modules/__snapshots__/GenerateModuleObjCpp-test.js.snap

Lines changed: 320 additions & 32 deletions
Large diffs are not rendered by default.

packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import type {
1414
NamedShape,
15+
NativeModuleEventEmitterShape,
1516
NativeModuleFunctionTypeAnnotation,
1617
NativeModuleParamTypeAnnotation,
1718
NativeModulePropertyShape,
@@ -23,6 +24,7 @@ import type {AliasResolver} from './Utils';
2324

2425
const {unwrapNullable} = require('../../parsers/parsers-commons');
2526
const {wrapOptional} = require('../TypeUtils/Java');
27+
const {toPascalCase} = require('../Utils');
2628
const {createAliasResolver, getModules} = require('./Utils');
2729

2830
type FilesOutput = Map<string, string>;
@@ -32,11 +34,13 @@ function FileTemplate(
3234
packageName: string,
3335
className: string,
3436
jsName: string,
37+
eventEmitters: string,
3538
methods: string,
3639
imports: string,
3740
}>,
3841
): string {
39-
const {packageName, className, jsName, methods, imports} = config;
42+
const {packageName, className, jsName, eventEmitters, methods, imports} =
43+
config;
4044
return `
4145
/**
4246
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
@@ -65,11 +69,28 @@ public abstract class ${className} extends ReactContextBaseJavaModule implements
6569
return NAME;
6670
}
6771
68-
${methods}
72+
${eventEmitters}${eventEmitters.length > 0 ? '\n\n' : ''}${methods}
6973
}
7074
`;
7175
}
7276

77+
function EventEmitterTemplate(
78+
eventEmitter: NativeModuleEventEmitterShape,
79+
imports: Set<string>,
80+
): string {
81+
return ` protected final void emit${toPascalCase(eventEmitter.name)}(${
82+
eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation'
83+
? `${translateEventEmitterTypeToJavaType(eventEmitter, imports)} value`
84+
: ''
85+
}) {
86+
mEventEmitterCallback.invoke("${eventEmitter.name}"${
87+
eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation'
88+
? ', value'
89+
: ''
90+
});
91+
}`;
92+
}
93+
7394
function MethodTemplate(
7495
config: $ReadOnly<{
7596
abstract: boolean,
@@ -102,6 +123,34 @@ function MethodTemplate(
102123
103124
type Param = NamedShape<Nullable<NativeModuleParamTypeAnnotation>>;
104125
126+
function translateEventEmitterTypeToJavaType(
127+
eventEmitter: NativeModuleEventEmitterShape,
128+
imports: Set<string>,
129+
): string {
130+
switch (eventEmitter.typeAnnotation.typeAnnotation.type) {
131+
case 'StringTypeAnnotation':
132+
return 'String';
133+
case 'NumberTypeAnnotation':
134+
case 'FloatTypeAnnotation':
135+
case 'DoubleTypeAnnotation':
136+
case 'Int32TypeAnnotation':
137+
return 'double';
138+
case 'BooleanTypeAnnotation':
139+
return 'boolean';
140+
case 'ObjectTypeAnnotation':
141+
case 'TypeAliasTypeAnnotation':
142+
imports.add('com.facebook.react.bridge.ReadableMap');
143+
return 'ReadableMap';
144+
case 'ArrayTypeAnnotation':
145+
imports.add('com.facebook.react.bridge.ReadableArray');
146+
return 'ReadableArray';
147+
default:
148+
throw new Error(
149+
`Unsupported eventType for ${eventEmitter.name}. Found: ${eventEmitter.typeAnnotation.typeAnnotation.type}`,
150+
);
151+
}
152+
}
153+
105154
function translateFunctionParamToJavaType(
106155
param: Param,
107156
createErrorMessage: (typeName: string) => string,
@@ -533,6 +582,9 @@ module.exports = {
533582
packageName: normalizedPackageName,
534583
className,
535584
jsName: moduleName,
585+
eventEmitters: spec.eventEmitters
586+
.map(eventEmitter => EventEmitterTemplate(eventEmitter, imports))
587+
.join('\n\n'),
536588
methods: methods.filter(Boolean).join('\n\n'),
537589
imports: Array.from(imports)
538590
.sort()

packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import type {
1414
NamedShape,
15+
NativeModuleEventEmitterShape,
1516
NativeModuleFunctionTypeAnnotation,
1617
NativeModuleParamTypeAnnotation,
1718
NativeModulePropertyShape,
@@ -54,9 +55,11 @@ const HostFunctionTemplate = ({
5455

5556
const ModuleClassConstructorTemplate = ({
5657
hasteModuleName,
58+
eventEmitters,
5759
methods,
5860
}: $ReadOnly<{
5961
hasteModuleName: string,
62+
eventEmitters: $ReadOnlyArray<NativeModuleEventEmitterShape>,
6063
methods: $ReadOnlyArray<{
6164
propertyName: string,
6265
argCount: number,
@@ -69,7 +72,21 @@ ${methods
6972
.map(({propertyName, argCount}) => {
7073
return ` methodMap_["${propertyName}"] = MethodMetadata {${argCount}, __hostFunction_${hasteModuleName}SpecJSI_${propertyName}};`;
7174
})
72-
.join('\n')}
75+
.join('\n')}${
76+
eventEmitters.length > 0
77+
? eventEmitters
78+
.map(eventEmitter => {
79+
return `
80+
eventEmitterMap_["${eventEmitter.name}"] = std::make_shared<AsyncEventEmitter<folly::dynamic>>();`;
81+
})
82+
.join('')
83+
: ''
84+
}${
85+
eventEmitters.length > 0
86+
? `
87+
setEventEmitterCallback(params.instance);`
88+
: ''
89+
}
7390
}`.trim();
7491
};
7592

@@ -438,7 +455,7 @@ module.exports = {
438455
.map(hasteModuleName => {
439456
const {
440457
aliasMap,
441-
spec: {methods},
458+
spec: {eventEmitters, methods},
442459
} = nativeModules[hasteModuleName];
443460
const resolveAlias = createAliasResolver(aliasMap);
444461

@@ -457,6 +474,7 @@ module.exports = {
457474
'\n\n' +
458475
ModuleClassConstructorTemplate({
459476
hasteModuleName,
477+
eventEmitters,
460478
methods: methods
461479
.map(({name: propertyName, typeAnnotation}) => {
462480
const [{returnTypeAnnotation, params}] =

packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/index.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {MethodSerializationOutput} from './serializeMethod';
1515

1616
const {createAliasResolver, getModules} = require('../Utils');
1717
const {serializeStruct} = require('./header/serializeStruct');
18+
const {EventEmitterHeaderTemplate} = require('./serializeEventEmitter');
1819
const {serializeMethod} = require('./serializeMethod');
1920
const {serializeModuleSource} = require('./source/serializeModule');
2021
const {StructCollector} = require('./StructCollector');
@@ -24,10 +25,12 @@ type FilesOutput = Map<string, string>;
2425
const ModuleDeclarationTemplate = ({
2526
hasteModuleName,
2627
structDeclarations,
28+
eventEmitters,
2729
protocolMethods,
2830
}: $ReadOnly<{
2931
hasteModuleName: string,
3032
structDeclarations: string,
33+
eventEmitters: string,
3134
protocolMethods: string,
3235
}>) => `${structDeclarations}
3336
@protocol ${hasteModuleName}Spec <RCTBridgeModule, RCTTurboModule>
@@ -36,7 +39,13 @@ ${protocolMethods}
3639
3740
@end
3841
39-
@interface ${hasteModuleName}SpecBase : NSObject
42+
@interface ${hasteModuleName}SpecBase : NSObject {
43+
@protected
44+
facebook::react::EventEmitterCallback _eventEmitterCallback;
45+
}
46+
- (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallbackWrapper;
47+
48+
${eventEmitters}
4049
@end
4150
4251
namespace facebook::react {
@@ -191,6 +200,9 @@ module.exports = {
191200
ModuleDeclarationTemplate({
192201
hasteModuleName: hasteModuleName,
193202
structDeclarations: structStrs.join('\n'),
203+
eventEmitters: spec.eventEmitters
204+
.map(eventEmitter => EventEmitterHeaderTemplate(eventEmitter))
205+
.join('\n'),
194206
protocolMethods: methodSerializations
195207
.map(({protocolMethod}) => protocolMethod)
196208
.join('\n'),
@@ -204,6 +216,7 @@ module.exports = {
204216
hasteModuleName,
205217
generatedStructs,
206218
hasteModuleName,
219+
spec.eventEmitters,
207220
methodSerializations.filter(
208221
({selector}) => selector !== '@selector(constantsToExport)',
209222
),
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict
8+
* @format
9+
*/
10+
11+
import type {NativeModuleEventEmitterShape} from '../../../CodegenSchema';
12+
13+
const {toPascalCase} = require('../../Utils');
14+
15+
function getEventEmitterTypeObjCType(
16+
eventEmitter: NativeModuleEventEmitterShape,
17+
): string {
18+
switch (eventEmitter.typeAnnotation.typeAnnotation.type) {
19+
case 'StringTypeAnnotation':
20+
return 'NSString *_Nonnull';
21+
case 'NumberTypeAnnotation':
22+
return 'NSNumber *_Nonnull';
23+
case 'BooleanTypeAnnotation':
24+
return 'BOOL';
25+
case 'ObjectTypeAnnotation':
26+
case 'TypeAliasTypeAnnotation':
27+
return 'NSDictionary *';
28+
case 'ArrayTypeAnnotation':
29+
return 'NSArray<id<NSObject>> *';
30+
default:
31+
throw new Error(
32+
`Unsupported eventType for ${eventEmitter.name}. Found: ${eventEmitter.typeAnnotation.typeAnnotation.type}`,
33+
);
34+
}
35+
}
36+
37+
function EventEmitterHeaderTemplate(
38+
eventEmitter: NativeModuleEventEmitterShape,
39+
): string {
40+
return `- (void)emit${toPascalCase(eventEmitter.name)}${
41+
eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation'
42+
? `:(${getEventEmitterTypeObjCType(eventEmitter)})value`
43+
: ''
44+
};`;
45+
}
46+
47+
function EventEmitterImplementationTemplate(
48+
eventEmitter: NativeModuleEventEmitterShape,
49+
): string {
50+
return `- (void)emit${toPascalCase(eventEmitter.name)}${
51+
eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation'
52+
? `:(${getEventEmitterTypeObjCType(eventEmitter)})value`
53+
: ''
54+
}
55+
{
56+
_eventEmitterCallback("${eventEmitter.name}", ${
57+
eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation'
58+
? eventEmitter.typeAnnotation.typeAnnotation.type !==
59+
'BooleanTypeAnnotation'
60+
? 'value'
61+
: '[NSNumber numberWithBool:value]'
62+
: 'nil'
63+
});
64+
}`;
65+
}
66+
67+
module.exports = {
68+
EventEmitterHeaderTemplate,
69+
EventEmitterImplementationTemplate,
70+
};

packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/source/serializeModule.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,39 @@
1010

1111
'use strict';
1212

13+
import type {NativeModuleEventEmitterShape} from '../../../../CodegenSchema';
1314
import type {
1415
MethodSerializationOutput,
1516
StructParameterRecord,
1617
} from '../serializeMethod';
1718
import type {Struct} from '../StructCollector';
1819

20+
const {
21+
EventEmitterImplementationTemplate,
22+
} = require('./../serializeEventEmitter');
23+
1924
const ModuleTemplate = ({
2025
hasteModuleName,
2126
structs,
2227
moduleName,
28+
eventEmitters,
2329
methodSerializationOutputs,
2430
}: $ReadOnly<{
2531
hasteModuleName: string,
2632
structs: $ReadOnlyArray<Struct>,
2733
moduleName: string,
34+
eventEmitters: $ReadOnlyArray<NativeModuleEventEmitterShape>,
2835
methodSerializationOutputs: $ReadOnlyArray<MethodSerializationOutput>,
2936
}>) => `
3037
@implementation ${hasteModuleName}SpecBase
38+
${eventEmitters
39+
.map(eventEmitter => EventEmitterImplementationTemplate(eventEmitter))
40+
.join('\n')}
41+
42+
- (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallbackWrapper
43+
{
44+
_eventEmitterCallback = std::move(eventEmitterCallbackWrapper->_eventEmitterCallback);
45+
}
3146
@end
3247
3348
${structs
@@ -58,7 +73,23 @@ namespace facebook::react {
5873
argCount,
5974
}),
6075
)
61-
.join('\n' + ' '.repeat(8))}
76+
.join('\n' + ' '.repeat(8))}${
77+
eventEmitters.length > 0
78+
? eventEmitters
79+
.map(eventEmitter => {
80+
return `
81+
eventEmitterMap_["${eventEmitter.name}"] = std::make_shared<AsyncEventEmitter<id>>();`;
82+
})
83+
.join('')
84+
: ''
85+
}${
86+
eventEmitters.length > 0
87+
? `
88+
setEventEmitterCallback([&](const std::string &name, id value) {
89+
static_cast<AsyncEventEmitter<id> &>(*eventEmitterMap_[name]).emit(value);
90+
});`
91+
: ''
92+
}
6293
}
6394
} // namespace facebook::react`;
6495

@@ -112,12 +143,14 @@ function serializeModuleSource(
112143
hasteModuleName: string,
113144
structs: $ReadOnlyArray<Struct>,
114145
moduleName: string,
146+
eventEmitters: $ReadOnlyArray<NativeModuleEventEmitterShape>,
115147
methodSerializationOutputs: $ReadOnlyArray<MethodSerializationOutput>,
116148
): string {
117149
return ModuleTemplate({
118150
hasteModuleName,
119151
structs: structs.filter(({context}) => context !== 'CONSTANTS'),
120152
moduleName,
153+
eventEmitters,
121154
methodSerializationOutputs,
122155
});
123156
}

0 commit comments

Comments
 (0)