Blog Post

How to download and display a PDF document in Flutter with Nutrient

Illustration: How to download and display a PDF document in Flutter with Nutrient

Our Flutter PDF SDK enables you to open and edit a local PDF document on Android and iOS. In this article, you’ll learn how to download a PDF file from the internet and then display and edit that file.

In this example, you’ll use a Dart package called Dio to download a PDF file from the internet. Then, you’ll use Nutrient Flutter SDK to view, annotate, and edit that file.

For this tutorial, you should have already followed the installation instructions for Flutter and have the environment correctly set up. You can doublecheck the requirements for running Nutrient Flutter SDK in our getting started guide. If you don’t already have Android Studio and Xcode installed, you’ll need to install those as well. If you’re in a hurry, you can use our ready-to-run example project and skip to the end of the blog where the example is run.

Information

If you encounter any issues, Flutter offers a useful command for detecting the most common environment problems. From your terminal app, type flutter doctor -v, and flutter doctor will scan your system and provide a short description of the most common problems.

The use case

The use case is straightforward: You want to download a PDF file from a URL and then display it. You also want to optionally edit and annotate the file. In the video below, you can see how to first download a PDF file using Dio and then open it using the Flutter PDF SDK.

Now, it’s time to look at the steps involved.

Setting up the project

First, you’ll set up a new project and configure it.

  1. Create an empty project — Use the flutter create command to set up a brand-new project for your example. If you already have a project you’re integrating Nutrient into, you can skip this step:

    flutter create --org com.pspdfkit.flutter_example pspdfkit_flutter_example
  2. Add the dependencies — Add the required dependencies to the pubspec.yaml file.

    Navigate to the newly created example folder:

    cd pspdfkit_flutter_example

    Open the pubspec.yaml file:

    open pubspec.yaml

    Then, add the following lines corresponding to the plugins you want to add. Here, the path_provider plugin helps fetch the temporary directory on both Android and iOS — you’ll be storing the downloaded PDF file in the temporary directory:

    dependencies:
      flutter:
        sdk: flutter
    + pspdfkit_flutter:
    + path_provider:
    + dio: ^4.0.0
  3. Fetch the dependencies — Run flutter packages get to fetch the dependencies.

Android and iOS configuration

In addition to the steps above, you also have to configure both Android and iOS. This is detailed in the next sections.

Android configuration

  1. Make sure you’re in the newly created Flutter project directory.

  2. Open the app’s Gradle build file, android/app/build.gradle:

    open android/app/build.gradle
  3. Modify the minimum SDK version and enable multidex. All this is done inside the android section:

    android {
    defaultConfig {
    -   minSdkVersion 16
    +   minSdkVersion 21
    +   multiDexEnabled true
    ...
    }

iOS configuration

  1. Make sure you’re in the newly created Flutter project directory.

  2. Open the Xcode project’s workspace file:

    open ios/Runner.xcworkspace
  3. Set the iOS deployment target to 14.0 or higher.

    deployment-target

  4. Change View controller-based status bar appearance to YES in Info.plist. view-controller-appearance

  5. Open iOS/Podfile:

    open ios/Podfile
  6. In Podfile, update the platform to iOS 14 and add the PSPDFKit podspec:

    -# platform :ios, '9.0'
    +# platform :ios, '14.0'
    
    target 'Runner' do
      use_frameworks!
      use_modular_headers!
    
      flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
    + pod 'PSPDFKit', podspec:'https://my.pspdfkit.com/pspdfkit-ios/latest.podspec'
    end

With that, you’ve finished configuring both platforms. Now, it’s time to actually start building the app!

Updating the code

After setting up your project, you can get to the core part of the app. You’ll create a very simple app, with one button to download a file from the internet, and another button to display that file using Nutrient.

For the sake of simplicity, you won’t be adding elaborate error handling code or a mechanism to prevent users from downloading multiple copies of the file. Keep in mind that for production apps, you should definitely make sure that all possible error paths are taken care of. There should always be a recovery path (such as the ability to restart a download, delete a corrupted file, etc.) in case something goes wrong.

In the code snippet, there are comments in relevant places, which will help shed light on the reasoning behind the respective lines of code.

Open lib/main.dart and replace it with the following code snippet:

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pspdfkit_flutter/pspdfkit.dart';

// Filename of the PDF you'll download and save.
const fileName = '/pspdfkit-flutter-quickstart-guide.pdf';

// URL of the PDF file you'll download.
const imageUrl = 'https://pspdfkit.com/downloads' + fileName;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Download and Display a PDF',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Download and Display a PDF'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  // Track the progress of a downloaded file here.
  double progress = 0;

  // Track if the PDF was downloaded here.
  bool didDownloadPDF = false;

  // Show the progress status to the user.
  String progressString = 'File has not been downloaded yet.';

  // This method uses Dio to download a file from the given URL
  // and saves the file to the provided `savePath`.
  Future download(Dio dio, String url, String savePath) async {
    try {
      Response response = await dio.get(
        url,
        onReceiveProgress: updateProgress,
        options: Options(
            responseType: ResponseType.bytes,
            followRedirects: false,
            validateStatus: (status) { return status! < 500; }
            ),
      );
      var file = File(savePath).openSync(mode: FileMode.write);
      file.writeFromSync(response.data);
      await file.close();

	    // Here, you're catching an error and printing it. For production
	    // apps, you should display the warning to the user and give them a
	    // way to restart the download.
    } catch (e) {
      print(e);
    }
  }

  // You can update the download progress here so that the user is
  // aware of the long-running task.
  void updateProgress(done, total) {
    progress = done / total;
    setState(() {
      if (progress >= 1) {
        progressString = '✅ File has finished downloading. Try opening the file.';
        didDownloadPDF = true;
      } else {
        progressString = 'Download progress: ' + (progress * 100).toStringAsFixed(0) + '% done.';
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'First, download a PDF file. Then open it.',
            ),
            TextButton(
              // Here, you download and store the PDF file in the temporary
              // directory.
              onPressed: didDownloadPDF ? null : () async {
                  var tempDir = await getTemporaryDirectory();
                  download(Dio(), imageUrl, tempDir.path + fileName);
                },
              child: Text('Download a PDF file'),
            ),
            Text(
              progressString,
            ),
            TextButton(
              // Disable the button if no PDF is downloaded yet. Once the
              // PDF file is downloaded, you can then open it using Nutrient.
              onPressed: !didDownloadPDF ? null : () async {
                  var tempDir = await getTemporaryDirectory();
                  await Pspdfkit.present(tempDir.path + fileName);
                },
              child: Text('Open the downloaded file using PSPDFKit'),
            ),
          ],
        ),
      ),
    );
  }
}

With the code updated, it’s time to move on to the next step.

Running the example

Finally, you can go ahead and run the example.

First check the emulators that flutter has by running:

flutter emulators

This command will output the list of the emulators, which should look something like the following:

apple_ios_simulator • iOS Simulator  • Apple  • ios
Pixel_4_API_30      • Pixel 4 API 30 • Google • android

To start the Android emulator, run the following command. Replace Pixel_4_API_30 with the title of the emulator you have on your machine:

flutter emulators --launch Pixel_4_API_30

To start the iOS simulator, run the following command:

flutter emulators --launch apple_ios_simulator

After the emulator launches, start the app:

flutter run

That’s it!! You should be able to download a PDF file from the internet and view it using Nutrient. You can also add annotations such as text, markup, and ink to the file. Additionally, Nutrient provides built-in support for a variety of PDF features such as bookmarks, outlines, and printing.

Conclusion

We hope this post helped you with downloading and opening PDF documents in your Flutter project. Although we didn’t cover a lot of the features of our Flutter PDF library in this example, you can experiment with them in the example project itself. Some of the main features are:

  • Annotation support — Your users can create, update, and delete annotations.
  • Interactive forms — Nutrient Flutter SDK comes with form editing support, so your users call fill out forms in a PDF document.
  • Digital signatures — Digital signatures are also supported. They’re used to verify the authenticity of a signed PDF.
  • Long-term support — At Nutrient, we release regular updates to add new features and enhancements; fix bugs; and maintain compatibility with the latest Flutter changes, dependencies, and operating system updates. Nutrient Flutter SDK supports the latest Android and iOS SDKs.
  • Great documentation and easy integration — We care a lot about our documentation and constantly improve our guides and integration steps. We also continuously strive to make the integration fast and smooth. Because feature parity is extremely important to us, we always try to make our features available for both Android and iOS via the same Dart API.

If you have any questions about our PDF SDKs, don’t hesitate to reach out to us. We’re happy to help!

FAQ

Here are a few frequently asked questions about downloading and displaying PDF documents in Flutter.

How can I download and display a PDF document in Flutter using Nutrient?

To download and display a PDF in Flutter using Nutrient, first use a package like Dio to download the PDF file from the internet and save it to the device. Then, integrate Nutrient Flutter SDK to open and display the PDF within your app.

Can I customize the PDF viewer in Flutter with Nutrient?

Yes, Nutrient offers extensive customization options for the PDF viewer, including toolbar configurations, annotation tools, and UI theming to align with your app’s design.

What are some common use cases for displaying PDFs in Flutter applications?

Common use cases include viewing and editing eBooks, manuals, reports, contracts, and other types of documents that users might need to access or interact with on their mobile devices.

What should I consider for performance when displaying PDFs in Flutter using Nutrient?

Key performance considerations include optimizing PDF loading times, managing memory efficiently, and ensuring smooth interactions with large or complex PDF documents. Nutrient provides tools to help manage these aspects effectively.

Related products
Share post
Free trial Ready to get started?
Free trial