Calling a Android Native UI component method from React native Js code

10,460

Solution 1

As per the pointer given by @agent_hunt.

check this blog for explaination

I have used ui manager commands in SignatureCaptureViewManager. Posting my solutions

public class SignatureCaptureViewManager extends ViewGroupManager<SignatureCaptureMainView> {
private Activity mCurrentActivity;

public static final String PROPS_SAVE_IMAGE_FILE="saveImageFileInExtStorage";
public static final String PROPS_VIEW_MODE = "viewMode";

public static final int COMMAND_SAVE_IMAGE = 1;


public SignatureCaptureViewManager(Activity activity) {
    mCurrentActivity = activity;
}

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

@ReactProp(name = PROPS_SAVE_IMAGE_FILE)
public void setSaveImageFileInExtStorage(SignatureCaptureMainView view, @Nullable Boolean saveFile) {
    Log.d("React View manager setSaveFileInExtStorage:", "" + saveFile);
    if(view!=null){
        view.setSaveFileInExtStorage(saveFile);
    }
}

@ReactProp(name = PROPS_VIEW_MODE)
public void setViewMode(SignatureCaptureMainView view, @Nullable String viewMode) {
    Log.d("React View manager setViewMode:", "" + viewMode);
    if(view!=null){
        view.setViewMode(viewMode);
    }
}

@Override
public SignatureCaptureMainView createViewInstance(ThemedReactContext context) {
    Log.d("React"," View manager createViewInstance:");
    return new SignatureCaptureMainView(context, mCurrentActivity);
}

@Override
public Map<String,Integer> getCommandsMap() {
    Log.d("React"," View manager getCommandsMap:");
    return MapBuilder.of(
            "saveImage",
            COMMAND_SAVE_IMAGE);
}

@Override
public void receiveCommand(
        SignatureCaptureMainView view,
        int commandType,
        @Nullable ReadableArray args) {
    Assertions.assertNotNull(view);
    Assertions.assertNotNull(args);
    switch (commandType) {
        case COMMAND_SAVE_IMAGE: {
            view.saveImage();
            return;
        }

        default:
            throw new IllegalArgumentException(String.format(
                    "Unsupported command %d received by %s.",
                    commandType,
                    getClass().getSimpleName()));
    }
}


}

For sending commands to ViewManager i have added this method in Signature Capture component

class SignatureCapture extends React.Component {

constructor() {
super();
this.onChange = this.onChange.bind(this);
}

onChange(event) {
console.log("Signature  ON Change Event");
if (!this.props.onSaveEvent) {
  return;
}

this.props.onSaveEvent({
  pathName: event.nativeEvent.pathName,
  encoded: event.nativeEvent.encoded,
});
 }

 render() {
  return (
   <SignatureView {...this.props} style={{flex: 1}} onChange=      {this.onChange} />
);
  }

saveImage(){
 UIManager.dispatchViewManagerCommand(
        React.findNodeHandle(this),
        UIManager.SignatureView.Commands.saveImage,
        [],
    );
   }
 }

SignatureCapture.propTypes = {
...View.propTypes,
rotateClockwise: PropTypes.bool,
square:PropTypes.bool,
saveImageFileInExtStorage: PropTypes.bool,
viewMode:PropTypes.string
};

  var SignatureView = requireNativeComponent('SignatureView',   SignatureCapture, {
 nativeOnly: {onChange: true}
 });

 module.exports = SignatureCapture;

This is how i am using SignatureCapture component in my parent Signature component

class Signature extends Component {

render() {

    return (
        <View style={{ flex: 1, flexDirection: "column" }}>

            <SignatureCapture
                style={{ flex: 8 }}
                ref="sign",
                onSaveEvent={this._onSaveEvent}
                saveImageFileInExtStorage={false}
                viewMode={"portrait"}/>

            <TouchableHighlight style={{ flex: 2 }}
                onPress={() => { this.saveSign() } } >
                <Text>Save</Text>
            </TouchableHighlight>

        </View>
    );
}
// Calls Save method of native view and triggers onSaveEvent callback
saveSign() {
    this.refs["sign"].saveImage();        
}

_onSaveEvent(result) {
    //result.encoded - for the base64 encoded png
    //result.pathName - for the file path name
    console.log(result);
  }

  }

 export default Signature;

Solution 2

I needed a solution that let me return values from my component instance method (Promises in my case). Using receiveCommand didn't allow me to do this.

I was able to solve this using UIManagerModule.addUIBlock, similar to https://stackoverflow.com/a/31936516/194065:

public class MyViewModule extends ReactContextBaseJavaModule {

    public static final String TAG = MyViewModule.class.getSimpleName();

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

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

    @ReactMethod
    public void someMethod(final int viewId, final Promise promise) {
        withMyView(viewId, promise, new MyViewHandler() {
            @Override
            public void handle(MyView view) {
                String value = view.someMethod();
                promise.resolve(value)
            }
        });
    }

    private void withMyView(final int viewId, final Promise promise, final MyViewHandler handler) {
        UIManagerModule uiManager = getReactApplicationContext().getNativeModule(UIManagerModule.class);
        uiManager.addUIBlock(new UIBlock() {
            @Override
            public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
                View view = nativeViewHierarchyManager.resolveView(viewId);
                if (view instanceof MyView) {
                    MyView myView = (MyView) view;
                    handler.handle(myView);
                }
                else {
                    Log.e(TAG, "Expected view to be instance of MyView, but found: " + view);
                    promise.reject("my_view", "Unexpected view type");
                }
            }
        });
    }


}

Usage:

import React, { Component } from 'react';
import { NativeModules, requireNativeComponent, findNodeHandle } from "react-native";
const MyViewFunctions = NativeModules.MyView;


class MyView extends Component {

    someMethod() {
        MyViewFunctions.someMethod(findNodeHandle(this.nativeCmp));
    }

    render() {
        return (
            <RCTMyView
                ref={cmp => this.nativeCmp = cmp}
                {...this.props}
            />
        );
}

const RCMyView = requireNativeComponent('RCMyView', MyView);

export default MyView;

Solution 3

Please see instructions for exactly similar problem at https://github.com/facebook/react-native/pull/4438#issuecomment-163533312

Share:
10,460
John
Author by

John

Android, React and React-Native enthusiast , And code freak. Love to program and solve problems

Updated on June 08, 2022

Comments

  • John
    John about 2 years

    I have created a CustomView SignatureView.java which extends LinearLayout for capturing signature in Android Native.

    And created SignatureCapturePackage.java and SignatureCaptureViewManager.java

    public class SignatureCaptureMainView extends LinearLayout {
    
         .... 
    
        public void saveImage(){
                   //Save image to file 
         }
    }
    

    this the Package class

    public class SignatureCapturePackage implements ReactPackage {
          private Activity mCurrentActivity;
    
          public RSSignatureCapturePackage(Activity activity) {
            mCurrentActivity = activity;
          }
    
          @Override
          public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
            return Arrays.<NativeModule>asList();
          }
    
          @Override
          public List<ViewManager> createViewManagers(ReactApplicationContext reactApplicationContext) {
            return Arrays.<ViewManager>asList(new SignatureCaptureViewManager(mCurrentActivity));
          }
    
          @Override
          public List<Class<? extends JavaScriptModule>> createJSModules() {
            return Arrays.asList();
          }
        }
    

    this is the ViewManager class

     public class SignatureCaptureViewManager extends      ViewGroupManager<SignatureCaptureMainView> {
        private Activity mCurrentActivity;
    
        public static final String PROPS_SAVE_IMAGE_FILE="saveImageFileInExtStorage";
        public static final String PROPS_VIEW_MODE = "viewMode";
    
        public RSSignatureCaptureViewManager(Activity activity) {
            mCurrentActivity = activity;
        }
    
        @Override
        public String getName() {
            return "SignatureView";
        }
    
        @ReactProp(name = PROPS_SAVE_IMAGE_FILE)
        public void setSaveImageFileInExtStorage(SignatureCaptureMainView view, @Nullable Boolean saveFile) {
            Log.d("React View manager setSaveFileInExtStorage:", "" + saveFile);
            if(view!=null){
                view.setSaveFileInExtStorage(saveFile);
            }
        }
    
        @ReactProp(name = PROPS_VIEW_MODE)
        public void setViewMode(SignatureCaptureMainView view, @Nullable String viewMode) {
            Log.d("React View manager setViewMode:", "" + viewMode);
            if(view!=null){
                view.setViewMode(viewMode);
            }
        }
    
        @Override
        public SignatureCaptureMainView createViewInstance(ThemedReactContext context) {
            Log.d("React"," View manager createViewInstance:");
            return new SignatureCaptureMainView(context, mCurrentActivity);
        }
    
    
      }
    

    This is Signature.js bundle

    var React = require('react-native');
      var {
        PropTypes,
        requireNativeComponent,
        View,
      } = React;
    
      class SignatureCapture extends React.Component {
    
        constructor() {
          super();
          this.onChange = this.onChange.bind(this);
        }
    
        onChange(event) {
          console.log("Signature  ON Change Event");
          if (!this.props.onSaveEvent) {
            return;
          }
    
          this.props.onSaveEvent({
            pathName: event.nativeEvent.pathName,
            encoded: event.nativeEvent.encoded,
          });
        }
    
        render() {
          return (
            <SignatureView {...this.props} style={{flex: 1}} onChange={this.onChange} />
          );
        }
    
        save(){
    
        }
      }
    
      SignatureCapture.propTypes = {
        ...View.propTypes,
        saveImageFileInExtStorage: PropTypes.bool,
        viewMode:PropTypes.string
      };
    
      var SignatureView = requireNativeComponent('SignatureView', SignatureCapture, {
        nativeOnly: {onChange: true}
      });
    
      module.exports = SignatureCapture;
    

    I am using the Module in ReactNative like this

    <SignatureCapture
                    onSaveEvent={this._onSaveEvent}
                    saveImageFileInExtStorage={false}
                    viewMode={"portrait"}/>
    

    Everything worksFine. But i have to save the image only when some click event occurs in the react side. ie, i have to call SignatureCaptureMainView's saveImage() method from reactnative js code.

    How can i achieve it ?.Please help

  • Val
    Val almost 7 years
    How can I add MyViewModule into createViewManagers? It shows incompatible type MyViewModule with ViewManager.
  • Val
    Val almost 7 years
    And how to use MyViewFunctions?
  • Sean Adkinson
    Sean Adkinson almost 7 years
    Sorry, I had a typo. Should've been MyViewFunctions.someMethod in the component... The idea is that MyView is still registered as a custom view through the normal means, and that MyViewFunctions essentially just allows for static access to calling methods on those views. So you would still have a normal view manager, but you'd use MyViewFunctions to call methods on those views, by passing in the id of the view that you want to call the method on. Let me know if that doesn't make sense.
  • Adamski
    Adamski about 6 years
    Great answer, hadn't found this anywhere else.
  • Cristiano Coelho
    Cristiano Coelho about 4 years
    Hello from the future. Is this still the preferred way to do it? React native docs are so poorly written on the Android side, and native libraries use this (and other two different ways) to implement this. I wonder what's the currently accepted/recommended way for this.