How to implement a React Native UI component method in Android

4.6k Views Asked by At

It's clear to me that for react-native native modules we can use the @ReactMethod to export a method and call it from JSX, but how do we do the same thing in react-native native UI components?

In the documentation I only see @ReactProp being mentioned. If @ReactMethod is not working, how do I access a property of my native UI component from JSX then? (On iOS this can be done on native ui components with RCT_EXPORT_METHOD but on Android is something similar possible?)

Thank you.

2

There are 2 best solutions below

5
On BEST ANSWER

Ok I ended up creating a Module, and passing a UI Component reference on its constructor:

Here's my UI component:

public class RCTACCalendarManager extends ViewGroupManager<RCTACCalendar> {
    public static final String REACT_CLASS = "RCTACCalendar";
    private RCTACCalendar mCalendarInstance;

    public RCTACCalendarManager(ReactApplicationContext reactContext) {
        super();
    }

    @Override
    public String getName() {
        return REACT_CLASS;
    }

    @Override
    public RCTACCalendar createViewInstance(ThemedReactContext context) {
        mCalendarInstance = new RCTACCalendar(context);
        return mCalendarInstance;
    }

    public RCTACCalendar getCalendarInstance() { // <-- returns the View instance
        return mCalendarInstance;
    }
}

Here's the Module I created for that component:

public class RCTACCalendarModule extends ReactContextBaseJavaModule {
    private RCTACCalendar mCalendarInstance;

    public RCTACCalendarModule(ReactApplicationContext reactContext, RCTACCalendarManager calManager) {
        super(reactContext);
        if (calManager != null) {
            mCalendarInstance = calManager.getCalendarInstance();
        }
    }

    @Override
    public String getName() {
        return "ACCalendarManager";
    }

    @ReactMethod
    public void mySuperDuperFunction(Promise promise) {
        if (mCalendarInstance != null) {
            mCalendarInstance.mySuperDuperFunction(promise); // <-- Magic
        }
    }
}

and here's how I combine those two together in my Package declaration:

public class RCTACCalendarPackage implements ReactPackage {
    private RCTACCalendarManager mCalManager;

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        if (mCalManager == null) {
            mCalManager = new RCTACCalendarManager(reactContext);
        }
        return Arrays.<NativeModule>asList(new RCTACCalendarModule(reactContext, mCalManager));
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        if (mCalManager == null) {
            mCalManager = new RCTACCalendarManager(reactContext);
        }
        return Arrays.<ViewManager>asList(mCalManager);
    }
}

It works like a charm.

2
On

The existing answer works great if you only need to support Android, but I found it didn't work when I was trying to integrate with iOS as well. I burnt quite a lot of time trying to wrangle this method into iOS, so I'll recommend what I came up with instead: using the UIManager that comes with react-native.

React Native Component

// ComponentWithNativeFunctionality.js

import {UIManager, findNodeHandle} from 'react-native';

class ComponentWithNativeFunctionality extends React.Component {
  const myRef = React.createRef();

  functionToCall = () => {
    UIManager.dispatchViewManagerCommand(
      findNodeHandle(this.myRef.current),
      "nameOfFunctionToCallInNativeLand",
      [/* additional arguments */]
    );
  }

  render() {
    return <NativeComponentView ref={this.myRef} />
  }
}

Android

// YourViewManager.java

public class YourViewManager extends SimpleViewManager<YourView> {
    // ...

    @Override
    public void receiveCommand(@NonNull YourView view, String commandId, @Nullable ReadableArray args) {
        super.receiveCommand(view, commandId, args);
            switch (commandId) {
                case "nameOfFunctionToCallInNativeLand":
                    view.nameOfFunctionToCallInNativeLand();
                    break;
            }
        }
    }
}

iOS (with Swift)

  1. Add #import "React/RCTUIManager.h" to your Bridging-Header.h
// YourViewManager.m

@interface RCT_EXTERN_MODULE(YourViewManagerClass, RCTViewManager)

//...

RCT_EXTERN_METHOD(
    nameOfFunctionToCallInNativeLand: (nonnull NSNumber *)node
)

@end
// YourViewManagerClass.swift

@objc(YourViewManagerClass)
class YourViewManagerClass: RCTViewManager {
    @objc func nameOfFunctionToCallInNativeLand(_ node: NSNumber) -> Void {
        DispatchQueue.main.async {
            let component = self.bridge.uiManager.view(
              forReactTag: node
            ) as! MisnapCameraView
            component.nameOfFunctionToCallInNativeLand()
        }
    }
}

Another note: you can't pass in a Promise like you can with modules. You will have to pass in a unique ID generated in JS, and then once your action is done, fire an event to bubble back the result to JS with the ID attached to the event.