Forked from chourobin/0-bridging-react-native-cheatsheet.md
Created
February 24, 2022 10:12
-
-
Save thuydao/8ee008c9bd4e46ef6964527586923d25 to your computer and use it in GitHub Desktop.
React Native Bridging Cheatsheet
// index.ios.js
videoPlayer.seekTo(100, (error, someData) => {
if (error) {
console.error(error)
} else {
console.log(someData)
}
})
// index.android.js
videoPlayer.seekTo(
100,
(msg) => {
console.error(msg)
},
(someData) => {
console.log(someData)
}
)
RCT_EXPORT_METHOD(seekTo:(double)time callback:(RCTResponseSenderBlock)callback)
{
NSArray *someData;
callback(@[[NSNull null], someData]); // (error, someData) in js
}
@ReactMethod
public void seekTo(double time, Callback errorCallback, Callback successCallback) {
try {
successCallback.invoke(someData);
} catch (Exception e) {
errorCallback.invoke(e.getMessage());
}
}
Keep in mind:
- Events share namespace (so come up with an app-wide naming convention)
// VideoPlayer.js
class VideoPlayer extends React.PureComponent {
_onEnd = (event) => {
if (!this.props.onEnd) {
return;
}
this.props.onEnd(event.nativeEvent)
}
render() {
// Re-assign onEnd to the private _onEnd and store it in `nativeProps`
const nativeProps = {
...this.props,
onEnd: this._onEnd,
}
return (
<RCTVideo
{...nativeProps}
/>
)
}
}
const RCTVideo = requireNativeComponent('RCTVideo', VideoPlayer)
VideoPlayer.propTypes = {
/**
* Callback that is called when the current player item ends.
*/
onEnd: PropTypes.func,
}
Notes: Bubbling events are like DOM events so that a parent component can capture an event fired by its child. Generally these are UI-related, like "the user touched this box". Direct events are not bubbled and are intended for more abstract events like "this image failed to load".
// VideoPlayer.h
#import <UIKit/UIKit.h>
#import <React/RCTView.h>
@interface VideoPlayer : UIView
// or RCTBubblingEventBlock
@property (nonatomic, copy) RCTDirectEventBlock onEnd;
@end
// VideoPlayerManager.m (inherits from RCTViewManager)
@implementation VideoPlayerManager
RCT_EXPORT_MODULE()
RCT_EXPORT_VIEW_PROPERTY(onEnd, RCTDirectEventBlock)
- (UIView *)view
{
...
}
/*
* `VideoPlayerManager` acts as the delegate of all of the `VideoPlayer` views. This is just one
* pattern and it's perfectly fine to call `onEnd` from the `VideoPlayer` directly.
*/
- (void)onEnd:(VideoPlayer *)videoPlayer
{
if (!videoPlayer.onEnd) {
return;
}
videoPlayer.onEnd(@{ @"some-data" : @1 });
}
@end
Define a custom event mapping by overriding getExportedCustomDirectEventTypeConstants
in the
manager class:
// VideoPlayerManager.java
@Override
public @Nullable Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of(
"onEnd",
MapBuilder.of("registrationName", "onEnd")
);
}
Dispatch the event:
// VideoPlayerView.java
private void dispatchOnEnd() {
WritableMap event = Arguments.createMap();
...
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
getId(),
"onEnd",
event
);
}
Keep in mind:
- Events share namespace (so come up with an app-wide naming convention)
import { NativeEventEmitter, NativeModules } from 'react-native'
const videoPlayer = NativeModules.VideoPlayer
const videoPlayerEmitter = new NativeEventEmitter(VideoPlayer)
const subscription = videoPlayerEmitter.addListener('video-progress', (data) => console.log(data.progress))
// Don't forget to unsubscribe, typically in `componentWillUnmount`
subscription.remove()
Important Note: Don't send events if there are no listeners attached. You will get a warning about spending unneccessary resources. Override
-startObserving
and-stopObserving
like in the example below:
// VideoPlayer.h
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface CalendarManager : RCTEventEmitter <RCTBridgeModule>
@end
// VideoPlayer.m
#import "VideoPlayer.h"
@implementation VideoPlayer
{
BOOL _hasListeners;
}
RCT_EXPORT_MODULE();
- (NSArray<NSString *> *)supportedEvents
{
return @[@"video-progress"];
}
// Will be called when this module's first listener is added.
- (void)startObserving
{
_hasListeners = YES;
}
// Will be called when this module's last listener is removed, or on dealloc.
- (void)stopObserving
{
_hasListeners = NO;
}
- (void)onVideoProgress
{
CGFloat progress = ...;
if (_hasListeners) {
[self sendEventWithName:@"video-progress" body:@{ @"progress": @(progress) }];
}
}
@end
private void onVideoProgress() {
WriteableMap params = Arguments.createMap();
...
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("video-progress", params);
}
async function loadVideo() {
try {
var videoLoaded = await VideoPlayer.loadVideo();
this.setState(videoLoaded: videoLoaded)
} catch (e) {
console.error(e)
}
}
loadVideo()
RCT_REMAP_METHOD(loadVideo, loadVideoWithResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
BOOL videoLoaded = ...; // could be any data type listed under https://facebook.github.io/react-native/docs/native-modules-ios.html#argument-types
if (videoLoaded) {
resolve(videoLoaded);
} else {
NSError *error = ...
reject(@"error", @"error description", error);
}
}
@ReactMethod loadVideo(Promise promise) {
try {
Boolean videoLoaded = ...; // could be any data type listed under https://facebook.github.io/react-native/docs/native-modules-android.html#argument-types
if (videoLoaded) {
promise.resolve(videoLoaded);
}
} catch (Exception e) {
promise.reject(ERROR, e);
}
}
render() {
<VideoPlayer loop={false} />
}
VideoPlayer.propTypes = {
loop: PropTypes.bool,
}
VideoPlayer.defaultProps = {
loop: false,
}
// BMEVideoManager.m
RCT_EXPORT_VIEW_PROPERTY(loop, BOOL);
@ReactProp(name = "loop")
public void setLoop(Boolean loop) {
...
}
import { NativeModules } from 'react-native'
const videoPlayer = NativeModules.VideoPlayer
videoPlayer.seekTo(100)
Keep in mind:
RCT_EXPORT_METHOD
exposes the name of the method up to the first colon in js land.- When many methods share the same name, use
RCT_REMAP_METHOD
to define a different name for js and map it to the native method. ie.RCT_REMAP_METHOD(differentMethodName, methodName:(BOOL)arg1 arg2:(BOOL)arg2)
.
// VideoPlayer.h
#import <React/RCTBridgeModule.h>
@interface VideoPlayer : NSObject <RCTBridgeModule>
@end
@implementation VideoPlayer
RCT_EXPORT_MODULE(); // or RCT_EXPLORT_MODULE(AwesomeVideoPlayer) -- custom name
RCT_EXPORT_METHOD(seekTo:(double)time)
{
// seek to time
}
@end
// VideoPlayer.java
package com.beme.react
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
public class VideoModule extends ReactContextBaseJavaModule {
public VideoModule(ReactApplicationContext reactContext) {
super(reactContext)
}
@Override
public String getName() {
return "VideoPlayer";
}
@ReactMethod
public void seekTo(double time) {
// seek to time
}
}
Register the module
// VideoPackage.java
package com.beme.react
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
public class VideoPackage implements ReactPackage {
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new VideoModule(reactContext));
return modules;
}
}
// MainApplication.java
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new VideoPackage()); // <-- Add this line with your package name.
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment