Blog Post

How to Build a Flutter PDF Viewer

Illustration: How to Build a Flutter PDF Viewer

Flutter is an open source hybrid technology developed by Google that allows you to create iOS and Android apps using a single codebase in Dart. In this blog post, I’ll cover the basics of how to implement a PDF viewer in Flutter that can handle single-page documents.

I’ll begin by discussing how to select a package from a set of open source PDF alternatives and what to consider. Later on, I’ll delve into the steps required to integrate the PDF package and to end up with a project you can build and run.

The Right PDF Flutter Package

The criteria for evaluating a PDF library depends on your use case. An open source library is a good starting point for exploring your options and achieving a working prototype. For more complex use cases, we at PSPDFKit offer a battle-tested Flutter PDF SDK that can be integrated in just a few steps.

As a first step, searching for the term pdf in the pub.dev repository reveals a wide variety of choices.

The results page contains a lot of information, but the most significant indicators to consider when measuring the quality of a package are the pub points and the popularity. Pub points are calculated based on the results of static analysis criteria of the Dart project — things like support for null safety, support for multiple platforms, compliance to Dart conventions, and more. Meanwhile, popularity shows how many developers are using a specific package.

Another useful piece of information is the date of the latest update. Well-maintained projects usually have a fairly recent date, and that means the authors are actively working on the project.

Near the latest update date, you’ll also find the name of the publisher. Some publishers may have a small check icon, which indicates a package comes from a verified publisher.

As mentioned above, searching for the keyword pdf in the official package repository produces a long list of results that must be filtered based on our needs. Some of the packages returned don’t provide a rendering feature, but instead focus on other operations — like text extraction and generating a PDF document — that are out of the scope of this blog post. The plugin we’re looking for must expose an API for rendering a PDF document for Android.

Among the top results that satisfy this requirement, you’ll see:

  • flutter_pdfview — A Flutter plugin that provides a PDFView widget on Android and iOS.

  • native_pdf_view — A Flutter plugin for rendering PDF files on web, macOS, Windows, Android, and iOS.

  • advance_pdf_viewer — A Flutter plugin for handling PDF files.

After a quick assessment of the three packages, flutter_pdfview proved to be the most suitable for the goal of our experiment of implementing a simple PDF viewer. It has an example included in the GitHub repository, the configuration steps are easy to follow, and under its hood, it’s based on a Google library called PDFium — and here at PSPDFKit, we love PDFium! ❤️

Building a PDF Viewer with flutter_pdfview

flutter_pdfview is a Flutter package that can render a native PDF view for iOS and Android. It exposes some configuration options to provide a customized experience, and there’s an example app included in the GitHub repository.

To keep the use case simple, this post will use flutter_pdfview to show a single-page PDF document on an Android device with API 30.

The main layout of the app will be a widget showing the rendered PDF document, and it’ll allow simple gestures like pinch-to-zoom and dragging the zoomed page.

Initial Setup

First of all, to be sure you have the latest Flutter version installed, type the following in your terminal app:

flutter upgrade

Then, create a new Flutter project by typing:

flutter create --org com.example.pdf_viewer_demo pdf_viewer_demo

Integration of flutter_pdfview

Integrating flutter_pdfview is seamless. From the pdf_viewer_demo folder, open pubspec.yaml, and add flutter_pdfview as a dependency:

...
dependencies:
  flutter:
    sdk: flutter
+ flutter_pdfview: ^1.2.1
...

ℹ️ Note: Be careful with the above, as spaces matter in YAML files.

flutter_pdfview also needs the auxiliary path_provider package as a dependency to access the Android local storage. Our example app will copy a PDF document from the assets folder (read-only at runtime) to the local storage and then open it. To add the path_provider package as a dependency, enter the following:

...
dependencies:
  flutter:
    sdk: flutter
  flutter_pdfview: ^1.2.1
+ path_provider: ^2.0.1
...

flutter_pdfview uses AndroidPdfViewer in the Android part to render PDF documents. To download the AndroidPdfViewer dependency, add the JCenter repository in the project’s Gradle build script. To do this, go to pdf_viewer_demo, open android/build.gradle, and add jcenter() in the repositories section:

...
allprojects {
    repositories {
        google()
        mavenCentral()
+       jcenter()
    }
}

In the root folder of the project, create a new directory called documents, and then copy a sample PDF document into it. Assuming the sample.pdf is in the Download folder, you can copy it to the documents folder by typing:

cp ~/Downloads/sample.pdf documents/sample.pdf

Specify the assets directory in pubspec.yaml:

flutter:
+  assets:
+    - documents/

Now it’s time to download the dependencies. From the terminal, run:

flutter pub get

Once the process finishes, navigate to the lib folder and open main.dart. Then, replace its contents with the following code:

import 'dart:async';
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter_pdfview/flutter_pdfview.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String pathPDF = "";

  @override
  void initState() {
    super.initState();
    fromAsset('documents/sample.pdf', 'sample.pdf').then((f) {
      setState(() {
        pathPDF = f.path;
      });
    });
  }

  Future<File> fromAsset(String asset, String filename) async {
    Completer<File> completer = Completer();
    try {
      var dir = await getApplicationDocumentsDirectory();
      File file = File("${dir.path}/$filename");
      var data = await rootBundle.load(asset);
      var bytes = data.buffer.asUint8List();
      await file.writeAsBytes(bytes, flush: true);
      completer.complete(file);
    } catch (e) {
      throw Exception('Error parsing asset file!');
    }

    return completer.future;
  }

  @override
  Widget build(BuildContext context) {
    final themeData = Theme.of(context);
    return MaterialApp(
      home: Scaffold(body: Builder(
        builder: (BuildContext context) {
          return Center(
              child: Column(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                ElevatedButton(
                  child: Text('Tap to Open Document',
                      style: themeData.textTheme.headline4
                          ?.copyWith(fontSize: 21.0)),
                  onPressed: () {
                    if (pathPDF.isNotEmpty) {
                      Navigator.push(
                        context,
                        MaterialPageRoute(
                          builder: (context) => PDFScreen(path: pathPDF),
                        ),
                      );
                    }
                  },
                )
              ]));
        },
      )),
    );
  }
}

class PDFScreen extends StatefulWidget {
  final String? path;

  const PDFScreen({Key? key, this.path}) : super(key: key);

  @override
  _PDFScreenState createState() => _PDFScreenState();
}

class _PDFScreenState extends State<PDFScreen> with WidgetsBindingObserver {
  final Completer<PDFViewController> _controller =
      Completer<PDFViewController>();
  int? pages = 0;
  int? currentPage = 0;
  bool isReady = false;
  String errorMessage = '';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Document"),
        actions: <Widget>[
          IconButton(
            icon: const Icon(Icons.share),
            onPressed: () {},
          ),
        ],
      ),
      body: Stack(
        children: <Widget>[
          PDFView(
            filePath: widget.path,
            onRender: (_pages) {
              setState(() {
                pages = _pages;
                isReady = true;
              });
            },
            onError: (error) {
              setState(() {
                errorMessage = error.toString();
              });
              print(error.toString());
            },
            onPageError: (page, error) {
              setState(() {
                errorMessage = '$page: ${error.toString()}';
              });
              print('$page: ${error.toString()}');
            },
            onViewCreated: (PDFViewController pdfViewController) {
              _controller.complete(pdfViewController);
            },
          ),
          errorMessage.isEmpty
              ? !isReady
                  ? const Center(
                      child: CircularProgressIndicator(),
                    )
                  : Container()
              : Center(
                  child: Text(errorMessage),
                )
        ],
      ),
    );
  }
}

To run the PDF viewer, make sure to connect a physical Android device or open an emulator from the Android Virtual Device Manager. Then from the command line, type:

flutter run

After a short building process, the PDF viewer app will be ready to use on the Android device. 🎉

Conclusion

In this blog post, we covered some factors to look at when selecting a PDF package from a set of open source packages, and we showed you how to build a PDF viewer using the open source Flutter PDF library, flutter_pdfview, which is good for solving simple use cases without too much hassle. However, AndroidPdfViewer — the library flutter_pdfview is based on — hasn’t received an update in three years, and it’s still published to JCenter, an artifact repository that’s now read-only.

For more complex customizations, PSPDFKit offers its own Flutter PDF library with a detailed getting started guide. PSPDFKit ships with many features out of the box, including:

  • A customizable UI that’s simple and easy to use.

  • Embeddable prebuilt tools to easily add functionality like annotating documents, adding digital signatures to a PDF form, and much more.

  • Multiple file type support — from image files (JPG, PNG, TIFF) to PDF documents.

It also features active development cycles with constant improvements and bug fixes. To get started with PSPDFKit for Flutter, check out our free trial. If you have any questions about our Flutter PDF library, please don’t hesitate to reach out to us. We’re happy to help!

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

Related Articles

Explore more
PRODUCTS  |  Flutter • Releases

PSPDFKit 3.3 for Flutter Adds Support for Flutter Version 3

DEVELOPMENT  |  Android • iOS • Cordova • Flutter • Ionic • React Native • Xamarin

Cross-Platform Mobile Frameworks — An iOS Engineer's Perspective

TUTORIALS  |  Flutter • Android • iOS • How To

How to Download and Display a PDF Document in Flutter with PSPDFKit