AVAILABLE NOW: Spring 2025 Release
By Maxwell Yang | 2020 Feb 09
Flutter is an open-source framework by Google for building native mobile apps. Flutter compiles Dart widgets into native components to provide users the fluid look and feel of traditional native apps. And its learn-once, write-anywhere codebase makes Flutter a popular choice to develop cross-platform. As Flutter runs on both native and the web, it helps with a faster time to market and UX consistency, but it retains flexibility for platform-specific logic.
Being fully cross-platform capable as well, the PDFTron SDK pairs nicely with Flutter. This post shows how to use Flutter and PDFTron to create a cross-platform PDF, MS Office, and image file viewer -- like the one embedded below!
Follow the steps below to create your own viewer for both native mobile platforms and the web, no servers required. Then we’ll show you how easy it is to use PDFTron to add more features, like annotations, signatures, redaction, and more.
First, follow the guide from the official Flutter site to ensure you have the web option enabled. Then run:
flutter create myapp
cd myapp
This creates an app that works on native and the web.
Next, we will add PDFTron's Flutter SDK as well as WebViewer into the app.
Follow the instructions here to add PDFTron's Flutter module to the app. Then, follow step 2-5(a) for Android, and step 2-4 for iOS.
Create a local.properties
file inside the android
folder with your Android SDK location, for example:
ndk.dir=/Users/<user-name>/Library/Android/sdk/ndk-bundle
sdk.dir=/Users/<user-name>/Library/Android/sdk
Note: replace with your actual path.
Download and unzip the WebViewer package, then place it in the root folder of this app.
In file web/index.html
, add a <script>
tag inside the <head>
tag:
<head>
...
<script src="Webviewer/lib/webviewer.min.js"></script>
...
</head>
Create a pdfviewer
folder in the lib
directory, then add the following files:
pdfviewer_interface.dart
- the interface of the viewerpdfviewer_web.dart
- the web implementation of the viewerpdfviewer_native.dart
- the native implementation of the viewerpdfviewer_unsupported.dart
- the viewer for unsupported platformsAdd in pdfviewer_interface.dart
:
import 'package:flutter/material.dart';
import 'pdfviewer_unsupported.dart'
if (dart.library.io) 'pdfviewer_native.dart'
if (dart.library.html) 'pdfviewer_web.dart';
abstract class PDFViewer extends Widget {
factory PDFViewer(String document) => getPDFViewer(document);
}
Add in pdfviewer_unsupported.dart
:
import 'pdfviewer_interface.dart';
PDFViewer getPDFViewer(String document) => throw UnsupportedError(
'Cannot create a viewer when platform is neither web nor mobile');
Add in pdfviewer_native.dart
:
import 'dart:async';
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:myapp/pdfviewer/pdfviewer_interface.dart';
import 'package:pdftron_flutter/pdftron_flutter.dart';
import 'package:permission_handler/permission_handler.dart';
class NativeViewer extends StatefulWidget implements PDFViewer {
final String _document;
NativeViewer(this._document);
@override
_NativeViewerState createState() => _NativeViewerState();
}
class _NativeViewerState extends State<NativeViewer> {
String _version = 'Unknown';
@override
void initState() {
super.initState();
initPlatformState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
String version;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
PdftronFlutter.initialize("your_pdftron_license_key");
version = await PdftronFlutter.version;
} on PlatformException {
version = 'Failed to get platform version.';
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_version = version;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
child: DocumentView(
onCreated: onDocumentViewCreated,
),
));
}
void onDocumentViewCreated(DocumentViewController controller) {
if (Platform.isIOS) {
showViewer(controller);
} else {
launchWithPermission(controller);
}
}
Future<void> launchWithPermission(DocumentViewController controller) async {
Map<PermissionGroup, PermissionStatus> permissions =
await PermissionHandler().requestPermissions([PermissionGroup.storage]);
if (granted(permissions[PermissionGroup.storage])) {
showViewer(controller);
}
}
bool granted(PermissionStatus status) {
return status == PermissionStatus.granted;
}
void showViewer(DocumentViewController controller) {
// shows how to disable functionality
// var disabledElements = [Buttons.shareButton, Buttons.searchButton];
// var disabledTools = [Tools.annotationCreateLine, Tools.annotationCreateRectangle];
var config = Config();
// config.disabledElements = disabledElements;
// config.disabledTools = disabledTools;
// PdftronFlutter.openDocument(_document, config: config);
// opening without a config file will have all functionality enabled.
controller.openDocument(widget._document, config: config);
}
}
PDFViewer getPDFViewer(String document) => NativeViewer(document);
Add in pdfviewer_web.dart
:
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'dart:html' as html;
import 'package:myapp/pdfviewer/pdfviewer_interface.dart';
class WebViewer extends StatefulWidget implements PDFViewer {
final String _document;
WebViewer(this._document);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
@override
_WebViewerState createState() => _WebViewerState();
}
class _WebViewerState extends State<WebViewer> {
String viewID = "webviewer-id";
html.DivElement _element;
@override
void initState() {
super.initState();
_element = html.DivElement()
..id = 'canvas'
..append(html.ScriptElement()
..text = """
const canvas = document.querySelector("flt-platform-view").shadowRoot.querySelector("#canvas");
WebViewer({
path: 'WebViewer/lib',
initialDoc: '${widget._document}'
}, canvas).then((instance) => {
// call apis here
});
""");
// ignore: undefined_prefixed_name
ui.platformViewRegistry
.registerViewFactory(viewID, (int viewId) => _element);
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
title: Text('PDFTron Flutter Demo'),
),
body: FractionallySizedBox(
widthFactor: 1,
heightFactor: 1,
child: Container(
alignment: Alignment.center,
child: HtmlElementView(
viewType: viewID,
),
),
));
}
}
PDFViewer getPDFViewer(String document) => WebViewer(document);
In main.dart
, this viewer component can now be used:
@override
Widget build(BuildContext context) {
String document =
"https://pdftron.s3.amazonaws.com/downloads/pl/PDFTRON_about.pdf";
return MaterialApp(
home: PDFViewer(document),
);
}
The above code will pick up the native component on mobile platforms and the WebViewer component on the web. The rest of your application can share the same code, both for business logic and the UI.
That's it!
You can find full source code from here.
Learn how to create a simple app using Flutter with the open source convenience wrapper for the Apryse mobile SDK. Read the blog.
As you can see, creating a cross-platform PDF viewer that runs on both the mobile and the web using PDFTron SDK isn’t complicated when using PDFTron's Flutter SDK and WebViewer. And your new viewer will come out-of-the-box supporting hundreds of unique features, from annotation to redaction, which you can optionally add or remove using the APIs.
Get started with PDFTron for Flutter and WebViewer, and check out other WebViewer documentation to see your many options for customizing and extending your cross-platform viewer!
Let us know what you build. And if you have any questions or comments, don’t hesitate to contact us.
Maxwell Yang
Share this post
PRODUCTS
Platform Integrations
End User Applications
Popular Content