Skip to content

Add an API

Maxwell-Yang-PDFTron edited this page Oct 13, 2020 · 6 revisions

title: Add an API for Flutter metaTitle: Add an API for Flutter | PDFTron SDK platforms: android ios languages: xml javascript html objectivec java

Introduction

The PDFTron iOSAndroid Flutter 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 Flutter.

There are 2 different ways on how to use PDFTron Flutter API on iOSAndroid.

  • Present a document via a plugin.
  • Show a PDFTron document view via a Widget.

This guide will demonstrate how to add a new functionality to both approaches.

Below we provide an example of how to add a "getPageCropBox" function to the Flutter interface, which returns a PTRect object containing information about the crop box of a specified page. You can follow the same pattern to add new functions that your Flutter app may need. The new functions could be simple ones, which expose one piece of functionality, or custom ones, that a series of native commands under the hood.

Prior to following this guide, we highly recommend you to go through the official guide here: Writing Platform-specific code to have a better understanding of the system.

Example: Adding the getPageCropBox function

1. Fork and clone PDFTron's Flutter Repo

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

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

2. Define the new function in Dart

The document_view.dart file handles function calls for the plugin.

Add the method in class DocumentViewManager. Note that since we use JSON to transfer the information between Flutter and the native code, one extra step of decoding is required here.

Future<PTRect> getPageCropBox(int pageNumber) {
    return _channel.invokeMethod('getPageCropBox', <String, dynamic>{'pageNumber': pageNumber}).then((value) => jsonDecode(value));
  }

The options.dart file contains constants and classes for convenience. For this exercise, We add a new class called PTRect in this file, for easier access of information.

class PTRect {
  double x1, y1, x2, y2, width, height;
  PTRect(this.x1, this.y1, this.x2, this.y2, this.width, this.height);

  factory PTRect.fromJson(dynamic json) {
    return PTRect(getDouble(json['x1']), getDouble(json['y1']), getDouble(json['x2']), getDouble(json['y2']), getDouble(json['width']), getDouble(json['height']));
  }

  // a helper for JSON number decoding
  static getDouble(dynamic value) {
    if (value is int) {
      return value.toDouble();
    } else {
      return value;
    }
  }
}

3. Receive the new function from method channel

First, define the function and argument constants in ios/PluginUtils.h:

// function
...
static NSString * const PTGetPageCropBoxKey = @"getPageCropBox";
...
// argument
...
static NSString * const PTPageNumberArgumentKey = @"pageNumber";
...

Then, you have 3 options:

Option 1

You would like the new function implemented for the plugin version.

Open the ios/PdftronFlutterPlugin.m file and add a new case for the new function to handleMethodCall:

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
    if ([call.method isEqualToString:PTGetPlatformVersionKey]) {
        ...
    } else if ([call.method isEqualToString:PTGetPageCropBoxKey]) {
        NSNumber *pageNumber = [PluginUtils PT_idAsNSNumber:call.arguments[PTPageNumberArgumentKey]];
        [self getPageCropBox:pageNumber resultToken:result];
    } else {
        ...
    }
}

Option 2

You would like the new function implemented for the widget version.

Open the ios/FlutterDocumentView.m file and add a new case for the new function to onMethodCall:

- (void)onMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result
{
    if ([call.method isEqualToString:PTOpenDocumentKey]) {
        ...
    } else if ([call.method isEqualToString:PTGetPageCropBoxKey]) {
        NSNumber *pageNumber = [PluginUtils PT_idAsNSNumber:call.arguments[PTPageNumberArgumentKey]];
        [self getPageCropBox:pageNumber resultToken:result];
    } else {
        ...
    }
}

Option 3

You would like the new function implemented for both version. Note that this option is only for convenience and easier maintenance, and you could always just use Option 1 and/or Option 2.

Open the ios/PluginUtils.m file and add a new case for the new function to handleMethodCall:

+ (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result documentViewController:(PTDocumentViewController *)docVC
{
    if ([call.method isEqualToString:PTImportAnnotationCommandKey]) {
        ...
    } else if ([call.method isEqualToString:PTGetPageCropBoxKey]) {
        NSNumber *pageNumber = [PluginUtils PT_idAsNSNumber:call.arguments[PTPageNumberArgumentKey]];
        [PluginUtils getPageCropBox:pageNumber resultToken:result documentViewController:docVC];
    } else {
        result(FlutterMethodNotImplemented);
    }
}

4. Implement the new function

Followed by step 3, 3 options will be listed below.

Option 1

You would like the new function implemented for the plugin version.

In the same file ios/PdftronFlutterPlugin.m, implement the new function:

- (void)getPageCropBox:(NSNumber *)pageNumber resultToken:(FlutterResult)result
{
    PTDocumentViewController *docVC = [self getDocumentViewController];
    NSError *error;
    [docVC.pdfViewCtrl DocLock:YES withBlock:^(PTPDFDoc * _Nullable doc) {
        if([doc HasDownloader])
        {
            // too soon
            NSLog(@"Error: The document is still being downloaded.");
            return;
        }
        
        PTPage *page = [doc GetPage:(int)pageNumber];
        if (page) {
            PTPDFRect *rect = [page GetCropBox];
            NSDictionary<NSString *, NSNumber *> *map = @{
                PTX1Key: @([rect GetX1]),
                PTY1Key: @([rect GetY1]),
                PTX2Key: @([rect GetX2]),
                PTY2Key: @([rect GetY2]),
                PTWidthKey: @([rect Width]),
                PTHeightKey: @([rect Height]),
            };
            NSData *jsonData = [NSJSONSerialization dataWithJSONObject:map options:0 error:nil];
            NSString *res = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
            
            result(res);
        }

    } error:&error];
    
    if(error)
    {
        NSLog(@"Error: There was an error while trying to get the page crop box. %@", error.localizedDescription);
    }
}

Option 2

You would like the new function implemented for the widget version.

In the same file ios/FlutterDocumentView.m, implement the new function:

- (void)getPageCropBox:(NSNumber *)pageNumber resultToken:(FlutterResult)result
{
    NSError *error;
    [self.documentViewController.pdfViewCtrl DocLock:YES withBlock:^(PTPDFDoc * _Nullable doc) {
        if([doc HasDownloader])
        {
            // too soon
            NSLog(@"Error: The document is still being downloaded.");
            return;
        }
        
        PTPage *page = [doc GetPage:(int)pageNumber];
        if (page) {
            PTPDFRect *rect = [page GetCropBox];
            NSDictionary<NSString *, NSNumber *> *map = @{
                PTX1Key: @([rect GetX1]),
                PTY1Key: @([rect GetY1]),
                PTX2Key: @([rect GetX2]),
                PTY2Key: @([rect GetY2]),
                PTWidthKey: @([rect Width]),
                PTHeightKey: @([rect Height]),
            };
            NSData *jsonData = [NSJSONSerialization dataWithJSONObject:map options:0 error:nil];
            NSString *res = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
            
            result(res);
        }

    } error:&error];
    
    if(error)
    {
        NSLog(@"Error: There was an error while trying to get the page crop box. %@", error.localizedDescription);
    }
}

Option 3

You would like the new function implemented for both version.

In the same file ios/PluginUtils.m, implement the new function:

+ (void)getPageCropBox:(NSNumber *)pageNumber resultToken:(FlutterResult)result documentViewController:(PTDocumentViewController *)docVC
{
    NSError *error;
    [docVC.pdfViewCtrl DocLock:YES withBlock:^(PTPDFDoc * _Nullable doc) {
        if([doc HasDownloader])
        {
            // too soon
            NSLog(@"Error: The document is still being downloaded.");
            return;
        }
        
        PTPage *page = [doc GetPage:(int)pageNumber];
        if (page) {
            PTPDFRect *rect = [page GetCropBox];
            NSDictionary<NSString *, NSNumber *> *map = @{
                PTX1Key: @([rect GetX1]),
                PTY1Key: @([rect GetY1]),
                PTX2Key: @([rect GetX2]),
                PTY2Key: @([rect GetY2]),
                PTWidthKey: @([rect Width]),
                PTHeightKey: @([rect Height]),
            };
            NSData *jsonData = [NSJSONSerialization dataWithJSONObject:map options:0 error:nil];
            NSString *res = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
            
            result(res);
        }

    } error:&error];
    
    if(error)
    {
        NSLog(@"Error: There was an error while trying to get the page crop box. %@", error.localizedDescription);
    }
}

The logic is to first get the current document view controller, lock the file and check whether the document is still being downloaded. If not, get the crop box and encode all the associated information into a JSON string as the result.

The actual implementation will depend on the actual functionality.

3. Receive the new function from method channel

First, define the function and argument constants in java/com/pdftron/pdftronflutter/PluginUtils.java:

    ...
    public static final String KEY_PAGE_NUMBER = "pageNumber";
    ...
    public static final String FUNCTION_GET_PAGE_CROP_BOX = "getPageCropBox";
    ...

Then, you have 3 options:

Option 1

You would like the new function implemented for the plugin version.

Open the /android/src/main/java/com/pdftron/pdftronflutter/PdftronFlutterPlugin.java file and add a new case for the new function to onMethodCall:

@Override
public void onMethodCall(MethodCall call, Result result) {
    switch (call.method) {
        case FUNCTION_GET_PLATFORM_VERSION:
        ...
        case FUNCTION_GET_PAGE_CROP_BOX:
            FlutterDocumentActivity flutterDocumentActivity = FlutterDocumentActivity.getCurrentActivity();
            Objects.requireNonNull(flutterDocumentActivity);
            Objects.requireNonNull(flutterDocumentActivity.getPdfDoc());
            Integer pageNumber = call.argument(KEY_PAGE_NUMBER);
            if (pageNumber != null) {
                try {
                    flutterDocumentActivity.getPageCropBox(pageNumber, result);
                } catch (JSONException ex) {
                    ex.printStackTrace();
                    result.error(Integer.toString(ex.hashCode()), "JSONException Error: " + ex, null);
                } catch (PDFNetException ex) {
                    ex.printStackTrace();
                    result.error(Long.toString(ex.getErrorCode()), "PDFTronException Error: " + ex, null);
                }
            }    
        default:
        ...
    }
}

Option 2

You would like the new function implemented for the widget version.

Open the /android/src/main/java/com/pdftron/pdftronflutter/FlutterDocumentView.java file and add a new case for the new function to onMethodCall:

@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
    switch (call.method) {
        case FUNCTION_OPEN_DOCUMENT:
        ...
        case FUNCTION_GET_PAGE_CROP_BOX:
            Objects.requireNonNull(documentView);
            Objects.requireNonNull(documentView.getPdfDoc());
            Integer pageNumber = call.argument(KEY_PAGE_NUMBER);
            if (pageNumber != null) {
                try {
                    documentView.getPageCropBox(pageNumber, result);
                } catch (JSONException ex) {
                    ex.printStackTrace();
                    result.error(Integer.toString(ex.hashCode()), "JSONException Error: " + ex, null);
                } catch (PDFNetException ex) {
                    ex.printStackTrace();
                    result.error(Long.toString(ex.getErrorCode()), "PDFTronException Error: " + ex, null);
                }
            }    
        default:
        ...
    }
}

Option 3

You would like the new function implemented for both version. Note that this option is only for convenience and easier maintenance, and you could always just use Option 1 and/or Option 2.

Open the /android/src/main/java/com/pdftron/pdftronflutter/PluginUtils.java file and add a new case for the new function to onMethodCall:

public static void onMethodCall(MethodCall call, MethodChannel.Result result, ViewActivityComponent component) {
    switch (call.method) {
        case FUNCTION_IMPORT_ANNOTATION_COMMAND:
        ...
        case FUNCTION_GET_PAGE_CROP_BOX: {
            checkFunctionPrecondition(component);
            Integer pageNumber = call.argument(KEY_PAGE_NUMBER);
            if (pageNumber != null) {
                try {
                    getPageCropBox(pageNumber, result, component);
                } catch (JSONException ex) {
                    ex.printStackTrace();
                    result.error(Integer.toString(ex.hashCode()), "JSONException Error: " + ex, null);
                } catch (PDFNetException ex) {
                    ex.printStackTrace();
                    result.error(Long.toString(ex.getErrorCode()), "PDFTronException Error: " + ex, null);
                }
            }
            break;
        }
        default:
            result.notImplemented();
            break;
    }
}

4. Implement the new function

Followed by step 3, 3 options will be listed below.

Option 1

You would like the new function implemented for the plugin version.

Open the /android/src/main/java/com/pdftron/pdftronflutter/FlutterDocumentActivity.java file and implement the new function:

public void getPageCropBox(int pageNumber, Result result) throws PDFNetException, JSONException {
    JSONObject jsonObject = new JSONObject();
    PDFDoc pdfDoc = getPdfDoc();
    if (pdfDoc == null) {
        result.error("InvalidState", "Activity not attached", null);
        return;
    }

    Rect rect = pdfDoc.getPage(pageNumber).getCropBox();
    jsonObject.put(KEY_X1, rect.getX1());
    jsonObject.put(KEY_Y1, rect.getY1());
    jsonObject.put(KEY_X2, rect.getX2());
    jsonObject.put(KEY_Y2, rect.getY2());
    jsonObject.put(KEY_WIDTH, rect.getWidth());
    jsonObject.put(KEY_HEIGHT, rect.getHeight());
    result.success(jsonObject.toString());
}

Option 2

You would like the new function implemented for the widget version.

Open the /android/src/main/java/com/pdftron/pdftronflutter/views/DocumentView.java file and implement the new function:

public void getPageCropBox(int pageNumber, MethodChannel.Result result) throws PDFNetException, JSONException {
    JSONObject jsonObject = new JSONObject();
    PDFDoc pdfDoc = getPdfDoc();
    if (pdfDoc == null) {
        result.error("InvalidState", "Activity not attached", null);
        return;
    }

    Rect rect = pdfDoc.getPage(pageNumber).getCropBox();
    jsonObject.put(KEY_X1, rect.getX1());
    jsonObject.put(KEY_Y1, rect.getY1());
    jsonObject.put(KEY_X2, rect.getX2());
    jsonObject.put(KEY_Y2, rect.getY2());
    jsonObject.put(KEY_WIDTH, rect.getWidth());
    jsonObject.put(KEY_HEIGHT, rect.getHeight());
    result.success(jsonObject.toString());
}

Option 3

You would like the new function implemented for both version.

In the same file /android/src/main/java/com/pdftron/pdftronflutter/PluginUtils.java, implement the new function:

private static void getPageCropBox(int pageNumber, MethodChannel.Result result, ViewActivityComponent component) throws PDFNetException, JSONException {
    JSONObject jsonObject = new JSONObject();
    PDFDoc pdfDoc = component.getPdfDoc();
    if (pdfDoc == null) {
        result.error("InvalidState", "Activity not attached", null);
        return;
    }

    Rect rect = pdfDoc.getPage(pageNumber).getCropBox();
    jsonObject.put(KEY_X1, rect.getX1());
    jsonObject.put(KEY_Y1, rect.getY1());
    jsonObject.put(KEY_X2, rect.getX2());
    jsonObject.put(KEY_Y2, rect.getY2());
    jsonObject.put(KEY_WIDTH, rect.getWidth());
    jsonObject.put(KEY_HEIGHT, rect.getHeight());
    result.success(jsonObject.toString());
}

The logic is to first get the current doc, get the crop box and encode all the associated information into a JSON string as the result.

The actual implementation will depend on the actual functionality.

5. Push the code and integrate the updated source

Now update your library with the new code.

The new function is now ready to use.

6. Access the new functionality

The app can now access the new API as follows:

Option 1 or Option 3 (Plugin version):

var cropBox = await PdftronFlutter.getPageCropBox(1);
print('The width of crop box for page 1 is: ' + cropBox.width.toString());

Or:

Option 2 and Option 3 (Widget version):

var cropBox = await controller.getPageCropBox(1);
print('The width of crop box for page 1 is: ' + cropBox.width.toString());

7. All done!

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

If you're also deploying on Android, you'll need to repeat steps 3 and 4 for Android. If you're also deploying on iOS, you'll need to repeat steps 3 and 4 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 props available for other PDFTron customers.

Clone this wiki locally