Blog Post

How to Customize Our Flutter PDF SDK

Reinhard Hafenscher
Illustration: How to Customize Our Flutter PDF SDK

Here at PSPDFKit, one of our main goals is to provide you with the tools you need to solve the problems you have. This can mean that while what we provide might not immediately solve an issue at hand, it at least gives you what you need to do it yourself. An example of one of these tools is that of our hybrid technologies — like the Flutter PDF SDK — which allow you to display PDFs but don’t expose the full API PSPDFKit for Android provides.

It’s easy to get started with Flutter, and our SDK allows you to open and display PDFs. However, there could be a scenario where you want to do more, like customize the UI, modify a document, or use any of our Android APIs. So today I want to walk you through how you can modify our Flutter SDK by creating a custom fork and modifying the Android parts of the project directly. Let’s get right to it!

Step 1 — Creating Your Fork

Since our Flutter project is publicly available on GitHub, we can use the built-in functionality of GitHub to create a fork of the repository. The advantage of doing it this way as opposed to just using a local copy is that we can easily pull changes made to the main project repository back into our fork.

If you don’t know how to create a fork of a repository, there’s a great GitHub guide you can follow here. It’ll teach you both how to fork the repository and how to keep your fork in sync with any changes made upstream.

Once you have your fork ready, move on to the next step.

Step 2 — Setting Up the Project Structure

Now that you have your fork, it’s time to set up the project structure. For ease of development, the best place to put your fork is in a directory that’s a sibling to our main app. The folder structure should look something like this:

app-root
  ∟ myapp
  ∟ pspdfkit-flutter

Now to set it up, do the following:

mkdir app-root
cd app-root
flutter create --org com.example.myapp myapp
git clone https://github.com/<YOUR-GITHUB-USER>/pspdfkit-flutter.git

In the code above, you created the root directory and a new Flutter app, and you cloned your fork. You can check if this is working correctly by running the app. Make sure you have a device connected or an emulator running before doing this:

cd myapp/
flutter run

You should now see the default Flutter app running on your connected device.

The default flutter app.

If everything looks good, you can continue to the next step.

Step 3 — Integrating PSPDFKit into Your Flutter App

The steps to integrate the PSPDFKit Flutter SDK in your app are actually the same as what’s described in our getting started guide. The only difference is how you add it to your pubspec.yaml:

pspdfkit_flutter:
    path: ../pspdfkit-flutter

If you were to integrate via Git, you’d be required to always push your changes and tell Flutter to update the dependencies. Instead, reference the local copy of your forked repository. This way, Flutter will pick up any changes you make to the project whenever they happen.

If everything went well, your app should now be able to open PDF files.

The PSPDFKit Flutter sample app.

With that, now it’s time to move on to the final step for today.

Step 4 — Modifying the Flutter SDK

To get a good overview of the important parts of the SDK, you’ll add a new parameter to the present method. The user will be able to enter some text, and FlutterPdfActivity will use this text to show a Toast message.

Start by adding the text field. Inside main.dart, apply the following changes:

@override
  Widget build(BuildContext context) {
    final ThemeData themeData = Theme.of(context);
+   final textController = TextEditingController();

    return new MaterialApp(
      home: new Scaffold(
          appBar: new AppBar(
            title: new Text('PSPDFKit Flutter Plugin example app'),
          ),
          body: Builder(
            // Create an inner `BuildContext` so that the `onPressed` methods
            // can refer to the `Scaffold` with `Scaffold.of()`.
            builder: (BuildContext context) {
              return Center(
                  child: new Column(
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                      children: [
                        new Text('PSPDFKit for $_frameworkVersion\n',
                            style: themeData.textTheme.display1
                                .copyWith(fontSize: 21.0)),
+                       new TextField(controller: textController, decoration: new InputDecoration.collapsed(hintText: "Message"), textAlign: TextAlign.center,),
                        new RaisedButton(
                            child: new Text('Tap to Open Document',
                                style: themeData.textTheme.display1
                                    .copyWith(fontSize: 21.0)),
-                           onPressed: () => showDocument(context))
+                           onPressed: () => showDocument(context, textController.text))
                      ]));
            },
          )),
    );
  }

This adds a new text field to the main screen and changes your call to showDocument to include the entered text. Next, you’ll need to update your showDocument and present methods to actually include this new parameter:

+ void present(String message) {
- void present() {
+    Pspdfkit.present("file:///sdcard/document.pdf", message);
-    Pspdfkit.present("file:///sdcard/document.pdf");
  }

+ void showDocument(BuildContext context, String message) async {
- void showDocument(BuildContext context) async {
    try {
      if (await Pspdfkit.checkAndroidWriteExternalStoragePermission()) {
+       present(message);
-       present();
      } else {
        AndroidPermissionStatus permissionStatus =
        await Pspdfkit.requestAndroidWriteExternalStoragePermission();
        if (permissionStatus == AndroidPermissionStatus.authorized) {
+         present(message);
-         present();
        } else if (permissionStatus == AndroidPermissionStatus.deniedNeverAsk) {
          _showToast(context);
        }
      }
    } on PlatformException catch (e) {
      print("Failed to open document: '${e.message}'.");
    }
  }

This just adds a new message parameter you pass along. If you run the app now and click the Tap to Open Document button, nothing will happen and an error will be logged. This happens because it’s now time to make some changes to the Flutter SDK itself to support this new parameter of the Pspdfkit.present() method.

Open up app-root/pspdfkit-flutter/lib/pspdfkit.dart and make the following changes:

/// Loads a [document] with a supported format using a given [configuration].
+ static Future<bool> present(String document, String message, [dynamic configuration]) async {
- static Future<bool> present(String document, [dynamic configuration]) async {
    await _channel.invokeMethod(
        'present',
+       <String, dynamic>{'document': document, 'message': message, 'configuration': configuration}
-       <String, dynamic>{'document': document, 'configuration': configuration}
    );
  }

Add your new message parameter here as well, and make sure to pass it further down. The _channel.invokeMethod() call passes the given parameters to the native Android and iOS SDK implementations.

In this blog post, we’re only looking at the Android side of things, but the iOS part works similarly. For the Android part, take a look at app-root/pspdfkit-flutter/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PspdfkitPlugin.java. You need to actually support the new parameter you added and pass it on to your FlutterPdfActivity:

case "present":
                String documentPath = call.argument("document");
                requireNotNullNotEmpty(documentPath, "Document path");
+               String message = call.argument("message");

                HashMap<String, Object> configurationMap = call.argument("configuration");
                ConfigurationAdapter configurationAdapter = new ConfigurationAdapter(context, configurationMap);

                documentPath = addFileSchemeIfMissing(documentPath);

                FlutterPdfActivity.setLoadedDocumentResult(result);
                boolean imageDocument = isImageDocument(documentPath);
                if (imageDocument) {
                    Intent intent = PdfActivityIntentBuilder.fromImageUri(context, Uri.parse(documentPath))
                            .activityClass(FlutterPdfActivity.class)
                            .configuration(configurationAdapter.build())
                            .build();
+                   intent.putExtra("MESSAGE_ARG", message);
                    context.startActivity(intent);

                } else {
                    Intent intent = PdfActivityIntentBuilder.fromUri(context, Uri.parse(documentPath))
                            .activityClass(FlutterPdfActivity.class)
                            .configuration(configurationAdapter.build())
                            .passwords(configurationAdapter.getPassword())
                            .build();
+                   intent.putExtra("MESSAGE_ARG", message);
                    context.startActivity(intent);
                }
                break;

You fetched the message argument and put it as an extra in your Intent to start FlutterPdfActivity. The final piece of the puzzle is to modify app-root/pspdfkit-flutter/android/src/main/java/com/pspdfkit/flutter/pspdfkit/FlutterPdfActivity.java itself. Here you want to read the message and show a Toast:

+ import android.widget.Toast;

  /**
   * For communication with the PSPDFKit plugin, keep a static reference to the current
   * activity.
   */
  public class FlutterPdfActivity extends PdfActivity {

      private static FlutterPdfActivity currentActivity;
      private static AtomicReference<Result> loadedDocumentResult = new AtomicReference<>();

      public static void setLoadedDocumentResult(Result result) {
          loadedDocumentResult.set(result);
      }

      @Override
      protected void onCreate(Bundle bundle) {
          super.onCreate(bundle);
          bindActivity();
+         final String receivedString = getIntent().getStringExtra("MESSAGE_ARG");
+         Toast.makeText(this, receivedString, Toast.LENGTH_SHORT).show();
      }

And with that, you’re done. You can now enter a custom text on the main screen that’s passed from Flutter to FlutterPdfActivity and shown in a Toast message.

Furthermore, since your fork provides you with direct access to modify FlutterPdfActivity, you can use all the customization options and features of PSPDFKit for Android, including:

Conclusion

While our Flutter SDK can’t do everything out of the box, I hope this gave you a rough overview of all the parts that make it work. Armed with this knowledge, you should be able to make our Flutter SDK do anything you require for your application, as long as PSPDFKit for Android supports it.

If you have any questions about PSPDFKit for Flutter, please don’t hesitate to reach out to us. We’re happy to help.

Related Products
PSPDFKit for Flutter

Product Page
Guides
Example Projects

Share Post
Free 60-Day Trial Try PSPDFKit in your app today.
Free Trial

Related Articles

Explore more
DEVELOPMENT  |  Android • Jetpack Compose

How to Implement Drag-to-Reorder List Functionality with Jetpack Compose

DEVELOPMENT  |  iOS • Android • Room • Kotlin Multiplatform • Tips

Seamless Room Database Integration for Kotlin Multiplatform Projects

PRODUCTS  |  Android • Releases

Android 2024.1 Update: Advanced Content Editing and Digital Signatures, Plus Expanded Jetpack Compose Support