Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save bramus/1536b9ec32dc9a02e417ff63e2a2e4ce to your computer and use it in GitHub Desktop.

Select an option

Save bramus/1536b9ec32dc9a02e417ff63e2a2e4ce to your computer and use it in GitHub Desktop.
React Native Bridging Cheatsheet

Callbacks (getting a response from js)

React

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

iOS

docs

RCT_EXPORT_METHOD(seekTo:(double)time callback:(RCTResponseSenderBlock)callback)
{
  NSArray *someData;
  callback(@[[NSNull null], someData]); // (error, someData) in js
}

Android

docs

@ReactMethod
public void seekTo(double time, Callback errorCallback, Callback successCallback) {
  try {
    successCallback.invoke(someData);
  } catch (Exception e) {
    errorCallback.invoke(e.getMessage());
  }
}

Go to Top

Events (for UI components)

Keep in mind:

  • Events share namespace (so come up with an app-wide naming convention)

React

// 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,
}

iOS

docs

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

Android

docs

example

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

Go to Top

Events

Keep in mind:

  • Events share namespace (so come up with an app-wide naming convention)

React

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()

iOS

docs

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

Android

docs

example

private void onVideoProgress() {
  WriteableMap params = Arguments.createMap();
  ...
  reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("video-progress", params);
}

Go to Top

Promises

React

async function loadVideo() {
  try {
    var videoLoaded = await VideoPlayer.loadVideo();
    this.setState(videoLoaded: videoLoaded)
  } catch (e) {
    console.error(e)
  }
}

loadVideo()

iOS

docs

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

Android

docs

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

Go to Top

Properties (for UI)

React

render() {
  <VideoPlayer loop={false} />
}

VideoPlayer.propTypes = {
  loop: PropTypes.bool,
}

VideoPlayer.defaultProps = {
  loop: false,
}

iOS

docs

// BMEVideoManager.m
RCT_EXPORT_VIEW_PROPERTY(loop, BOOL);

Android

docs

@ReactProp(name = "loop")
public void setLoop(Boolean loop) {
  ...
}

Go to Top

Simple one-off example

React

import { NativeModules } from 'react-native'
const videoPlayer = NativeModules.VideoPlayer
videoPlayer.seekTo(100)

iOS

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).

docs

// 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

Android

docs

// 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.
}

Go to Top

@Lemaro86
Copy link
Copy Markdown

Thank you for so cool info!
Is it really to use in the one example, (in react native wave form as case) to use both case: requireNativeComponent and NativeModules?
If I want to pass from js to native callback, I can't stop player, because I loose example of created OGWaveFormView. I hope that you or somebody can just add example with code and two methods in 1 component requireNativeComponent and NativeModules.
I can't use only 1, because in the requireNativeComponent It passed props and in NativeModules I created callback.

@mrdezzods
Copy link
Copy Markdown

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:

Is there a way to know if the event listener is attached in Android?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment