mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Add Java Turbo Module Event Emitter example (#44906)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/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 fbshipit-source-id: 04261d8885760f0e3b3c8c1931e0d56a5d33a0df
This commit is contained in:
committed by
Facebook GitHub Bot
parent
dba25fa966
commit
84a9f5e6c8
@@ -164,9 +164,8 @@ function throwIfEventEmitterTypeIsUnsupported(
|
||||
parser: Parser,
|
||||
nullable: boolean,
|
||||
untyped: boolean,
|
||||
cxxOnly: boolean,
|
||||
) {
|
||||
if (nullable || untyped || !cxxOnly) {
|
||||
if (nullable || untyped) {
|
||||
throw new UnsupportedModuleEventEmitterPropertyParserError(
|
||||
nativeModuleName,
|
||||
propertyName,
|
||||
@@ -174,7 +173,6 @@ function throwIfEventEmitterTypeIsUnsupported(
|
||||
parser.language(),
|
||||
nullable,
|
||||
untyped,
|
||||
cxxOnly,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,15 +100,12 @@ class UnsupportedModuleEventEmitterPropertyParserError extends ParserError {
|
||||
language: ParserType,
|
||||
nullable: boolean,
|
||||
untyped: boolean,
|
||||
cxxOnly: boolean,
|
||||
) {
|
||||
let message = `${language} interfaces extending TurboModule must only contain 'FunctionTypeAnnotation's or non nullable 'EventEmitter's. Further the EventEmitter property `;
|
||||
if (nullable) {
|
||||
message += `'${propertyValue}' must non nullable.`;
|
||||
} else if (untyped) {
|
||||
message += `'${propertyValue}' must have a concrete or void eventType.`;
|
||||
} else if (!cxxOnly) {
|
||||
message += `'${propertyValue}' is only supported in C++ Turbo Modules.`;
|
||||
}
|
||||
super(nativeModuleName, propertyValue, message);
|
||||
}
|
||||
|
||||
+1
-1
@@ -652,7 +652,7 @@ export interface Spec extends TurboModule {
|
||||
+onEvent6: EventEmitter<ObjectStruct[]>;
|
||||
}
|
||||
|
||||
export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModuleCxx');
|
||||
export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModule');
|
||||
|
||||
`;
|
||||
|
||||
|
||||
+1
-5
@@ -1531,11 +1531,7 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_EVENT_EM
|
||||
],
|
||||
'methods': []
|
||||
},
|
||||
'moduleName': 'SampleTurboModuleCxx',
|
||||
'excludedPlatforms': [
|
||||
'iOS',
|
||||
'android'
|
||||
]
|
||||
'moduleName': 'SampleTurboModule'
|
||||
}
|
||||
}
|
||||
}"
|
||||
|
||||
@@ -513,7 +513,6 @@ function buildEventEmitterSchema(
|
||||
parser,
|
||||
typeAnnotationNullable,
|
||||
typeAnnotationUntyped,
|
||||
cxxOnly,
|
||||
);
|
||||
const eventTypeResolutionStatus = resolveTypeAnnotationFN(
|
||||
typeAnnotation.typeParameters.params[0],
|
||||
|
||||
+1
-1
@@ -751,7 +751,7 @@ export interface Spec extends TurboModule {
|
||||
readonly onEvent6: EventEmitter<ObjectStruct[]>;
|
||||
}
|
||||
|
||||
export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModuleCxx');
|
||||
export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModule');
|
||||
|
||||
`;
|
||||
|
||||
|
||||
+1
-5
@@ -1731,11 +1731,7 @@ exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_EV
|
||||
],
|
||||
'methods': []
|
||||
},
|
||||
'moduleName': 'SampleTurboModuleCxx',
|
||||
'excludedPlatforms': [
|
||||
'iOS',
|
||||
'android'
|
||||
]
|
||||
'moduleName': 'SampleTurboModule'
|
||||
}
|
||||
}
|
||||
}"
|
||||
|
||||
@@ -543,6 +543,7 @@ public abstract class com/facebook/react/bridge/BaseJavaModule : com/facebook/re
|
||||
public static final field METHOD_TYPE_ASYNC Ljava/lang/String;
|
||||
public static final field METHOD_TYPE_PROMISE Ljava/lang/String;
|
||||
public static final field METHOD_TYPE_SYNC Ljava/lang/String;
|
||||
protected field mEventEmitterCallback Lcom/facebook/react/bridge/CxxCallbackImpl;
|
||||
public fun <init> ()V
|
||||
public fun <init> (Lcom/facebook/react/bridge/ReactApplicationContext;)V
|
||||
public fun canOverrideExistingModule ()Z
|
||||
|
||||
+8
@@ -13,6 +13,7 @@ import androidx.annotation.Nullable;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.infer.annotation.ThreadConfined;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.common.annotations.DeprecatedInNewArchitecture;
|
||||
import com.facebook.react.common.annotations.StableReactNativeAPI;
|
||||
@@ -54,6 +55,8 @@ public abstract class BaseJavaModule implements NativeModule {
|
||||
public static final String METHOD_TYPE_PROMISE = "promise";
|
||||
public static final String METHOD_TYPE_SYNC = "sync";
|
||||
|
||||
protected @Nullable CxxCallbackImpl mEventEmitterCallback;
|
||||
|
||||
private final @Nullable ReactApplicationContext mReactApplicationContext;
|
||||
|
||||
public BaseJavaModule() {
|
||||
@@ -129,4 +132,9 @@ public abstract class BaseJavaModule implements NativeModule {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
private final void setEventEmitterCallback(CxxCallbackImpl eventEmitterCallback) {
|
||||
mEventEmitterCallback = eventEmitterCallback;
|
||||
}
|
||||
}
|
||||
|
||||
+30
@@ -992,4 +992,34 @@ jsi::Value JavaTurboModule::invokeJavaMethod(
|
||||
}
|
||||
}
|
||||
|
||||
void JavaTurboModule::setEventEmitterCallback(
|
||||
jni::alias_ref<jobject> jinstance) {
|
||||
JNIEnv* env = jni::Environment::current();
|
||||
auto instance = jinstance.get();
|
||||
static jmethodID cachedMethodId = nullptr;
|
||||
if (cachedMethodId == nullptr) {
|
||||
jclass cls = env->GetObjectClass(instance);
|
||||
cachedMethodId = env->GetMethodID(
|
||||
cls,
|
||||
"setEventEmitterCallback",
|
||||
"(Lcom/facebook/react/bridge/CxxCallbackImpl;)V");
|
||||
}
|
||||
|
||||
auto eventEmitterLookup =
|
||||
[&](const std::string& eventName) -> AsyncEventEmitter<folly::dynamic>& {
|
||||
return static_cast<AsyncEventEmitter<folly::dynamic>&>(
|
||||
*eventEmitterMap_[eventName].get());
|
||||
};
|
||||
|
||||
jvalue arg;
|
||||
arg.l = JCxxCallbackImpl::newObjectCxxArgs([eventEmitterLookup = std::move(
|
||||
eventEmitterLookup)](
|
||||
folly::dynamic args) {
|
||||
auto eventName = args.at(0).asString();
|
||||
auto eventArgs = args.size() > 1 ? args.at(1) : nullptr;
|
||||
eventEmitterLookup(eventName).emit(std::move(eventArgs));
|
||||
}).release();
|
||||
env->CallVoidMethod(instance, cachedMethodId, arg);
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
+2
@@ -51,6 +51,8 @@ class JSI_EXPORT JavaTurboModule : public TurboModule {
|
||||
size_t argCount,
|
||||
jmethodID& cachedMethodID);
|
||||
|
||||
void setEventEmitterCallback(jni::alias_ref<jobject> instance);
|
||||
|
||||
private:
|
||||
// instance_ can be of type JTurboModule, or JNativeModule
|
||||
jni::global_ref<jobject> instance_;
|
||||
|
||||
+16
@@ -40,6 +40,22 @@ public abstract class NativeSampleTurboModuleSpec extends ReactContextBaseJavaMo
|
||||
return NAME;
|
||||
}
|
||||
|
||||
protected final void emitOnPress() {
|
||||
mEventEmitterCallback.invoke("onPress");
|
||||
}
|
||||
|
||||
protected final void emitOnClick(String value) {
|
||||
mEventEmitterCallback.invoke("onClick", value);
|
||||
}
|
||||
|
||||
protected final void emitOnChange(ReadableMap value) {
|
||||
mEventEmitterCallback.invoke("onChange", value);
|
||||
}
|
||||
|
||||
protected void emitOnSubmit(ReadableArray value) {
|
||||
mEventEmitterCallback.invoke("onSubmit", value);
|
||||
}
|
||||
|
||||
protected abstract Map<String, Object> getTypedExportedConstants();
|
||||
|
||||
@Override
|
||||
|
||||
+9
@@ -351,6 +351,15 @@ NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(
|
||||
1, __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectAssert};
|
||||
methodMap_["promiseAssert"] = MethodMetadata{
|
||||
0, __hostFunction_NativeSampleTurboModuleSpecJSI_promiseAssert};
|
||||
eventEmitterMap_["onPress"] =
|
||||
std::make_shared<AsyncEventEmitter<folly::dynamic>>();
|
||||
eventEmitterMap_["onClick"] =
|
||||
std::make_shared<AsyncEventEmitter<folly::dynamic>>();
|
||||
eventEmitterMap_["onChange"] =
|
||||
std::make_shared<AsyncEventEmitter<folly::dynamic>>();
|
||||
eventEmitterMap_["onSubmit"] =
|
||||
std::make_shared<AsyncEventEmitter<folly::dynamic>>();
|
||||
setEventEmitterCallback(params.instance);
|
||||
}
|
||||
|
||||
std::shared_ptr<TurboModule> SampleTurboModuleSpec_ModuleProvider(
|
||||
|
||||
+20
-1
@@ -104,7 +104,26 @@ public class SampleTurboModule extends NativeSampleTurboModuleSpec
|
||||
@Override
|
||||
public void voidFunc() {
|
||||
log("voidFunc", "<void>", "<void>");
|
||||
return;
|
||||
emitOnPress();
|
||||
emitOnClick("click");
|
||||
{
|
||||
WritableNativeMap map = new WritableNativeMap();
|
||||
map.putInt("a", 1);
|
||||
map.putString("b", "two");
|
||||
emitOnChange(map);
|
||||
}
|
||||
{
|
||||
WritableNativeArray array = new WritableNativeArray();
|
||||
WritableNativeMap map = new WritableNativeMap();
|
||||
map.putInt("a", 1);
|
||||
map.putString("b", "two");
|
||||
array.pushMap(map);
|
||||
WritableNativeMap map1 = new WritableNativeMap();
|
||||
map1.putInt("a", 3);
|
||||
map1.putString("b", "four");
|
||||
array.pushMap(map1);
|
||||
emitOnSubmit(array);
|
||||
}
|
||||
}
|
||||
|
||||
// This function returns {@link WritableMap} instead of {@link Map} for backward compat with
|
||||
|
||||
+14
-1
@@ -12,7 +12,10 @@ import type {
|
||||
RootTag,
|
||||
TurboModule,
|
||||
} from '../../../../Libraries/TurboModule/RCTExport';
|
||||
import type {UnsafeObject} from '../../../../Libraries/Types/CodegenTypes';
|
||||
import type {
|
||||
EventEmitter,
|
||||
UnsafeObject,
|
||||
} from '../../../../Libraries/Types/CodegenTypes';
|
||||
|
||||
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
|
||||
|
||||
@@ -21,7 +24,17 @@ export enum EnumInt {
|
||||
B = 42,
|
||||
}
|
||||
|
||||
export type ObjectStruct = {
|
||||
a: number,
|
||||
b: string,
|
||||
c?: ?string,
|
||||
};
|
||||
|
||||
export interface Spec extends TurboModule {
|
||||
+onPress: EventEmitter<void>;
|
||||
+onClick: EventEmitter<string>;
|
||||
+onChange: EventEmitter<ObjectStruct>;
|
||||
+onSubmit: EventEmitter<ObjectStruct[]>;
|
||||
// Exported methods.
|
||||
+getConstants: () => {|
|
||||
const1: boolean,
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
|
||||
import type {RootTag} from 'react-native/Libraries/ReactNative/RootTag';
|
||||
import type {EventSubscription} from 'react-native/Libraries/vendor/emitter/EventEmitter';
|
||||
|
||||
import styles from './TurboModuleExampleCommon';
|
||||
import * as React from 'react';
|
||||
@@ -68,6 +69,7 @@ type ErrorExamples =
|
||||
|
||||
class SampleTurboModuleExample extends React.Component<{||}, State> {
|
||||
static contextType: React$Context<RootTag> = RootTagContext;
|
||||
eventSubscriptions: EventSubscription[] = [];
|
||||
|
||||
state: State = {
|
||||
testResults: {},
|
||||
@@ -218,6 +220,30 @@ class SampleTurboModuleExample extends React.Component<{||}, State> {
|
||||
'The JSI bindings for SampleTurboModule are not installed.',
|
||||
);
|
||||
}
|
||||
this.eventSubscriptions.push(
|
||||
NativeSampleTurboModule.onPress(value => console.log('onPress: ()')),
|
||||
);
|
||||
this.eventSubscriptions.push(
|
||||
NativeSampleTurboModule.onClick(value =>
|
||||
console.log(`onClick: (${value})`),
|
||||
),
|
||||
);
|
||||
this.eventSubscriptions.push(
|
||||
NativeSampleTurboModule.onChange(value =>
|
||||
console.log(`onChange: (${JSON.stringify(value)})`),
|
||||
),
|
||||
);
|
||||
this.eventSubscriptions.push(
|
||||
NativeSampleTurboModule.onSubmit(value =>
|
||||
console.log(`onSubmit: (${JSON.stringify(value)})`),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
for (const subscription of this.eventSubscriptions) {
|
||||
subscription.remove();
|
||||
}
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
|
||||
Reference in New Issue
Block a user