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

3

13

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

Caird answered 28/3, 2016 at 15:43 Comment(0)
C
14

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;
Caird answered 29/3, 2016 at 5:52 Comment(0)
Z
7

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://mcmap.net/q/735135/-how-to-access-native-ui-components-39-instance-methods-in-react-native:

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;
Zoogeography answered 14/8, 2017 at 23:55 Comment(6)
How can I add MyViewModule into createViewManagers? It shows incompatible type MyViewModule with ViewManager.Fineman
And how to use MyViewFunctions?Fineman
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.Zoogeography
Great answer, hadn't found this anywhere else.Crewelwork
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.Aright
@CristianoCoelho 2022, This is still valid. github.com/facebook/react-native/issues/30587 RNN will never support this.Hyperbaric
F
2

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

Feuchtwanger answered 28/3, 2016 at 21:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.