Some test text!

Search
Hamburger Icon

Android / Guides / Add an API

Add an API for React Native

Introduction

The Apryse Android React Native API includes all of the most used functions and methods for viewing, annotating and saving PDF documents. However, it is possible your app may need access to APIs that are available as part of the native API, but are not directly available to React Native.

This guide provides an example of how to add the following to the React Native interface:

  • pageChangeOnTap prop that determines whether page will turn left or right when tapping corresponding edges.
  • getField function which retrieves information about a field using its name.
  • onLayoutChanged event listener that is raised when the layout of viewer has changed.

You can follow the same pattern to add new functions and props that your React Native app may need. The new additions could be simple ones, which expose one piece of functionality, or custom ones, that expose a series of native commands under the hood.

Prior to following this guide, we highly recommend you to go through the official guide here: Native UI Components to have a better understanding of the system.

1. Fork and clone Apryse's React Native Repo

The source is hosted on GitHub here: https://github.com/PDFTron/pdftron-react-native

Fork the project and clone a copy of the repository to your disk.

Adding the pageChangeOnTap prop

2. Add prop to DocumentView.tsx interface file

The DocumentView.tsx interface file lists all of the React Native props on the DocumentView component.

Add the prop declaration:

static propTypes = {
  pageChangeOnTap: PropTypes.bool,
};

For props that accept Config constants or arrays of Config constants, use the oneOf and arrayOf helper methods. These helpers return flexible types that allow custom checks for TypeScript users, while maintaining standard run-time checks for all users.

3. Define a ReactProp matches the TS declaration

Open file /android/src/main/java/com/pdftron/reactnative/viewmanagers/DocumentViewViewManager.java.

Add the method key that matches the TS declaration:

@ReactProp(name = "pageChangeOnTap")
public void setPageChangeOnTap(DocumentView documentView, boolean pageChangeOnTap) {
    documentView.setPageChangeOnTap(pageChangeOnTap);
}

4. Implement the method to set the property

Open file /android/src/main/java/com/pdftron/reactnative/views/DocumentView.java.

Add the implementation:

public void setPageChangeOnTap(boolean pageChangeOnTap) {
    Context context = getContext();
    if (context != null) {
        PdfViewCtrlSettingsManager.setAllowPageChangeOnTap(context, pageChangeOnTap);
    }
}

The actual implementation will depend on the actual functionality.

Adding the getField function

2. Add function to DocumentView.tsx interface file

The DocumentView.tsx interface file lists all of the React Native functions on the DocumentView component.

Add the function declaration:

getField = (fieldName: string): Promise<void | {fieldName: string, fieldValue?: any, fieldType?: string}> => {
    const tag = findNodeHandle(this._viewerRef);
    if(tag != null) {
      return DocumentViewManager.getField(tag, fieldName);
    }
    return Promise.resolve();
}

All functions return Promise<void | T> where T is the expected return type upon success. If the function returns void upon success, simply put Promise<void>.

While you are adding types, consider representing objects with reusable object types. You can use existing ones, or create a new type alias or interface in AnnotOptions.ts (see Object Types).

3. Receive the new method from JavaScript

Add this method to /android/src/main/java/com/pdftron/reactnative/modules/DocumentViewModule.java:

@ReactMethod
public void getField(final int tag, final String fieldName, final Promise promise) {
    getReactApplicationContext().runOnUiQueueThread(new Runnable() {
        @Override
        public void run() {
            try {
                WritableMap field = mDocumentViewInstance.getField(tag, fieldName);
                promise.resolve(field);
            } catch (Exception ex) {
                promise.reject(ex);
            }
        }
    });
}

4. Define the caller method

Add the following to /android/src/main/java/com/pdftron/reactnative/viewmanagers/DocumentViewViewManager.java:

public WritableMap getField(int tag, String fieldName) throws PDFNetException {
    DocumentView documentView = mDocumentViews.get(tag);
    if (documentView != null) {
        return documentView.getField(fieldName);
    } else {
        throw new PDFNetException("", 0L, getName(), "getField", "Unable to find DocumentView.");
    }
}

5. Implement the helper method

Add the following to /android/src/main/java/com/pdftron/reactnative/views/DocumentView.java:

public WritableMap getField(String fieldName) throws PDFNetException {
    PDFViewCtrl pdfViewCtrl = getPdfViewCtrl();
    PDFDoc pdfDoc = pdfViewCtrl.getDoc();

    WritableMap fieldMap = null;
    boolean shouldUnlock = false;

    try {
        pdfViewCtrl.docLockRead();
        shouldUnlock = true;

        Field field = pdfDoc.getField(fieldName);

        if (field != null && field.isValid()) {
            fieldMap = Arguments.createMap();
            int fieldType = field.getType();
            String typeString;
            switch (fieldType) {
                case Field.e_button:
                    typeString = FIELD_TYPE_BUTTON;
                    break;
                case Field.e_check:
                    typeString = FIELD_TYPE_CHECKBOX;
                    fieldMap.putBoolean(KEY_FIELD_VALUE, field.getValueAsBool());
                    break;
                case Field.e_radio:
                    typeString = FIELD_TYPE_RADIO;
                    fieldMap.putString(KEY_FIELD_VALUE, field.getValueAsString());
                    break;
                case Field.e_text:
                    typeString = FIELD_TYPE_TEXT;
                    fieldMap.putString(KEY_FIELD_VALUE, field.getValueAsString());
                    break;
                case Field.e_choice:
                    typeString = FIELD_TYPE_CHOICE;
                    fieldMap.putString(KEY_FIELD_VALUE, field.getValueAsString());
                    break;
                case Field.e_signature:
                    typeString = FIELD_TYPE_SIGNATURE;
                    break;
                default:
                    typeString = FIELD_TYPE_UNKNOWN;
                    break;
            }

            fieldMap.putString(KEY_FIELD_NAME, fieldName);
            fieldMap.putString(KEY_FIELD_TYPE, typeString);
        }
    } finally {
        if (shouldUnlock) {
            pdfViewCtrl.docUnlockRead();
        }
    }
    return fieldMap;
}

The logic is to get the PDF view controller and if the field names are valid, use the controller to get the fields from the current document.

Adding the onLayoutChanged event listener

2. Define the new listener in TS

Event listeners are handled in DocumentView.tsx. Add the following to the file:

static propTypes = {
    ...
    onLayoutChanged: func<() => void>(),
}

onChange = (event) => {
    if (event.nativeEvent.onLeadingNavButtonPressed) {
        ...
    } else if (event.nativeEvent.onLayoutChanged) {
      if (this.props.onLayoutChanged) {
        this.props.onLayoutChanged();
      }
    }
}

The onLayoutChanged event listener does not require any arguments. If your event listener requires arguments, access them using event.nativeEvent.exampleArg.

For event listeners and other props that accept functions, use the func helper method. This helper returns flexible types that allow custom checks for TypeScript users, while maintaining standard run-time checks for all users.

While you are adding types, consider representing objects with reusable object types. You can use existing ones, or create a new type alias or interface in AnnotOptions.ts (see Object Types).

3. Add event constant for Android

In android/src/main/java/com/pdftron/reactnative/utils/Constants.java, add a constant to represent the event.

// EVENTS
...
public static final String ON_LAYOUT_CHANGED = "onLayoutChanged";

4. Implement event listener

In /android/src/main/java/com/pdftron/reactnative/views/DocumentView.java, create a new event listener to send the event to JS. This event listener will call the existing method onReceiveNativeEvent when layout changes. Note that the event you want to implement may be a part of an existing event listener.

private View.OnLayoutChangeListener mLayoutChangedListener = new OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {

        WritableMap params = Arguments.createMap();
        params.putString(ON_LAYOUT_CHANGED, ON_LAYOUT_CHANGED);

        onReceiveNativeEvent(params);
    }
};

5. Set up and remove event listener

In the same file, /android/src/main/java/com/pdftron/reactnative/views/DocumentView.java, add the event listener to our PDFViewCtrl when the document has loaded, and remove the listener when it is no longer necessary.

@Override
public void onTabDocumentLoaded(String tag) {
    super.onTabDocumentLoaded(tag);
    ...
    getPdfViewCtrl().addOnLayoutChangeListener(mLayoutChangedListener);
    ...
}

@Override
protected void onDetachedFromWindow() {
    if (getPdfViewCtrl() != null) {
        ...
        getPdfViewCtrl().removeOnLayoutChangeListener(mLayoutChangedListener);
    }
    ...
}

The actual implementation will depend on the actual functionality.

Finishing steps

1. Push the code and integrate the updated source

Now npm install your forked version to your application.

The new functionality is now ready to use.

2. Access the new functionality

The app can now access the new API as follows:

onDocumentLoaded = () => {
    this._viewer.getField('someFieldName').then((field) => {
        if (field !== undefined) {
            console.log('field name:', field.fieldName);
            console.log('field value:', field.fieldValue);
            console.log('field type:', field.fieldType);
        }
    });
}

onLayoutChanged = () => {
    console.log("Layout changed");
}

<DocumentView
    ref={(c) => this._viewer = c}
    pageChangeOnTap={false}
    onDocumentLoaded={this.onDocumentLoaded}
    onLayoutChanged={this.onLayoutChanged}
    document={this.state.document}
/>

3. All done!

If you're only developing for Android, then you're all done!

If you're also deploying on iOS, you'll need to complete the necessary steps for iOS.

If you're developing for both iOS and Android, please consider submitting a PR, as upstreaming the change will simplify your developing and make the APIs available for other Apryse customers.

Get the answers you need: Chat with us