Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Java Turbo Module Event Emitter example #44906

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions packages/react-native-codegen/src/parsers/error-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,17 +164,15 @@ function throwIfEventEmitterTypeIsUnsupported(
parser: Parser,
nullable: boolean,
untyped: boolean,
cxxOnly: boolean,
) {
if (nullable || untyped || !cxxOnly) {
if (nullable || untyped) {
throw new UnsupportedModuleEventEmitterPropertyParserError(
nativeModuleName,
propertyName,
propertyValueType,
parser.language(),
nullable,
untyped,
cxxOnly,
);
}
}
Expand Down
3 changes: 0 additions & 3 deletions packages/react-native-codegen/src/parsers/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,6 @@ function buildEventEmitterSchema(
parser,
typeAnnotationNullable,
typeAnnotationUntyped,
cxxOnly,
);
const eventTypeResolutionStatus = resolveTypeAnnotationFN(
typeAnnotation.typeParameters.params[0],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
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;
Expand Down Expand Up @@ -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";

@Nullable protected CxxCallbackImpl mEventEmitterCallback;

private final @Nullable ReactApplicationContext mReactApplicationContext;

public BaseJavaModule() {
Expand Down Expand Up @@ -129,4 +132,9 @@ protected final ReactApplicationContext getReactApplicationContext() {
}
return null;
}

@DoNotStrip
private final void setEventEmitterCallback(CxxCallbackImpl eventEmitterCallback) {
mEventEmitterCallback = eventEmitterCallback;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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_;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,22 @@ public NativeSampleTurboModuleSpec(ReactApplicationContext reactContext) {
super(reactContext);
}

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);
}

@Override
public @Nonnull String getName() {
return NAME;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,26 @@ public double getRootTag(double arg) {
@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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -68,6 +69,7 @@ type ErrorExamples =

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

state: State = {
testResults: {},
Expand Down Expand Up @@ -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 {
Expand Down
Loading