Creating PDF files with Multiple Images in Flutter
Tags: flutter, tutorial, pdf
Published: 2021-05-25 17:44:19

### Introduction

This step-by-step tutorial will guide you on how to create and view a PDF file with multiple images.

It is a relatively easy process once you know the methods, and I will show you how to become natural for creating a pdf file in your projects.

For this tutorial, there are three essential packages needed.

* pdf ⇒ To create PDF documents
* path_provider ⇒ to retrieve the document directory
* syncfusion_flutter_pdfviewer ⇒ to view the created PDF files

Let's begin by either creating a new project or adding these dependencies to your current project setup.

``` yaml
dependencies:
  flutter:
    sdk: flutter
  path_provider: ^2.0.1
  pdf: ^3.3.0
  syncfusion_flutter_pdfviewer: ^19.1.64-beta
```

### 1. Creating a Simple PDF file

Inside the lib folder let's create a new page called **pdf_screen.dart**.

And inside the file create a StatefulWidget named PdfScreen.
``` dart
class PdfScreen extends StatefulWidget {
  @override
  _PdfScreenState createState() => _PdfScreenState();
}

class _PdfScreenState extends State<PdfScreen> {
  String pdfFile = '';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Visibility(
              visible: pdfFile.isNotEmpty,
              child: SfPdfViewer.file(File(pdfFile),
                  canShowScrollHead: false, canShowScrollStatus: false),
            ),
            TextButton(
                onPressed: () {
                },
                child: Text('Create a Pdf')),
          ],
        ),
      ),
    );
  }
}
``` 
A simple UI where we have a text button to create a PDF file for us and a **Visibility** widget to reveal the PDF viewer once the PDF file is created.

For viewing the PDF files, we are going to use the *syncfusion_flutter_pdfviewer* package's **SfPdfViewer.file** widget, and inside this widget we are going to create a file that has the path of the pdf that we have created.

Next, we are going to create 2 methods. 

* createPdfFile 
* savePdfFile

PDF package has its own widget catalog and in order to call these catalogs, we need to import the pdf widgets as a variable named **pw**.

``` dart
import 'package:pdf/widgets.dart' as pw;
``` 

To build a PDF layout, we are going to call the widgets by simply calling them `pw.<WidgetNme>`'.

In order to save a pdf file, we must create a pw.Document(). This special widget will hold the information of the created PDF, thus let's create a variable inside the `_PdfScreenState`.

``` dart
var pdf = pw.Document();
``` 

Here in the example, the idea is to create a MultiPage PDF  with an a4 format. Based on the number of images we are going to add, the number of pages might be more than one, therefore it makes sense to create a MultiPage widget right now.

Inside the build method of the MultiPage widget, there will be Column with a Header and Divider to divide the content from the header. In the next steps, we are going to add the content under the Divider inside this Column.

``` dart
createPdfFile() {
    pdf.addPage(pw.MultiPage(
        margin: pw.EdgeInsets.all(10),
        pageFormat: PdfPageFormat.a4,
        build: (pw.Context context) {
          return <pw.Widget>[
            pw.Column(
                crossAxisAlignment: pw.CrossAxisAlignment.center,
                mainAxisSize: pw.MainAxisSize.min,
                children: [
                  pw.Text('Create a Simple PDF',
                      textAlign: pw.TextAlign.center,
                      style: pw.TextStyle(fontSize: 26)),
                  pw.Divider(),
                ]),
          ];
        }));
  }
```

Afterward, let's create savePdfFile method.

It is an asynchronous method that awaits the directory of the platform of IOS or Android.

Later, the method creates a file with the name of the ID and the documentPath of the directory. You can name your file however you'd like but for multiple PDF creation, it is better to have unique names for each PDF file created.

Lastly, the method saves the PDF as a file.path and the value is given to the pdfFile variable inside the setState method to later update the UI.

``` dart
savePdfFile() async {
    Directory documentDirectory = await getApplicationDocumentsDirectory();

    String documentPath = documentDirectory.path;

    String id = DateTime.now().toString();

    File file = File("$documentPath/$id.pdf");

    file.writeAsBytesSync(await pdf.save());
    setState(() {
      pdfFile = file.path;
      pdf = pw.Document();
    });
  }
```



**Important note:** It is essential to assign **pw.Document()** to the pdf variable once again after saving a pdf file. If not done, the pdf will try to create another one on top of the previously created pdf file and that will cause a faulty pdf document to be created. 

**Bonus:** If you want to save your PDF file in bytes you can use the method below.
``` dart
  List<int> pdfBytes;

    pdfBytes = await file.readAsBytes();

    pdfFile base64Encode(pdfBytes);
```

Furthermore, create an async onPressed method.

In order to save a file, we need to await createPdfFile method first.

``` dart
 onPressed: () async {
                  await createPdfFile();
                  savePdfFile();
                },
```
Ok, now everything is set. You can press the 'Create a Pdf' button to see your first simple PDF file.

![simple_pdf.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1621964399340/0I2QQEekF.png)


### 2. Adding an Image

In the pdf package, you can add an Image with `pw.MemoryImage`. For this reason, we need to convert the images to Memory bytes.

Initially import packages below.
``` dart
import 'dart:typed_data';
import 'package:flutter/services.dart';
```
After that, convert **createPdfFile** into an async method and add these two variables. 

* the First variable converts an asset image into bytes
* the Second variable converts the bytes into a list of Uint8List bytes

``` dart
    final ByteData bytes = await rootBundle.load('assets/images/kitten.png');
    final Uint8List byteList = bytes.buffer.asUint8List();
```

Then add a pw.Image widget under the Divider.

``` dart
  pw.Image(
                pw.MemoryImage(
                  byteList,
                ),
                fit: pw.BoxFit.fitHeight)
```
MemoryImage takes byteList as a positional argument to render the image into the pdf file. 

Hot restart the application now and try to create another PDF file to see a file with an image inside it.


![image_pdf.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1621964417563/zS0N49Bo-.png)


### 3. Adding Multiple Images

In the final step of the tutorial, we are going to create a pdf build method that can generate a file with given multiple images.

To start with, we need to create a method that converts images to the Uint8List format. 

Inside the state widget, create an empty list for storing images as Uint8list. 

``` dart
List<Uint8List> imagesUint8list = [];
```

Next, let's refactor the createPdfFile method and extract a new method for getting bytes of images.

``` dart
 getImageBytes(String assetImage) async {
    final ByteData bytes = await rootBundle.load(assetImage);
    final Uint8List byteList = bytes.buffer.asUint8List();
    imagesUint8list.add(byteList);
  }
```

Now we will create a list of type **pw.Widgets** inside createPdfFile method that generates a Column with an image header and image itself.


``` dart
final List<pw.Widget> pdfImages = imagesUint8list.map((image) {
      return pw.Padding(
          padding: pw.EdgeInsets.symmetric(vertical: 20, horizontal: 10),
          child: pw.Column(
              crossAxisAlignment: pw.CrossAxisAlignment.center,
              mainAxisSize: pw.MainAxisSize.max,
              children: [
                pw.Text(
                    'Image'
                            ' ' +
                        (imagesUint8list
                                    .indexWhere((element) => element == image) +
                                1)
                            .toString(),
                    style: pw.TextStyle(fontSize: 22)),
                pw.SizedBox(height: 10),
                pw.Image(
                    pw.MemoryImage(
                      image,
                    ),
                    height: 400,
                    fit: pw.BoxFit.fitHeight)
              ]));
    }).toList();
``` 

Note: It is crucial to give a specific max size to image widgets, otherwise one image might overflow from the page format leading to a failed pdf creation.

In the final step, **createPdfFile** is going to first create a for loop to convert images into Uint8List.
Later it will create a list of images with headers.
Lastly, the **pdfImages** will be displayed inside the main column of the pdf as its children.


``` dart
 createPdfFile() async {
    //convert each image to Uint8List
    for (String image in assetImages) await getImageBytes(image);
    //create a list of images
    final List<pw.Widget> images = imagesUint8list.map((image) {
      return pw.Padding(
          padding: pw.EdgeInsets.symmetric(vertical: 20, horizontal: 10),
          child: pw.Column(
              crossAxisAlignment: pw.CrossAxisAlignment.center,
              mainAxisSize: pw.MainAxisSize.max,
              children: [
                pw.Text(
                    'Image'
                            ' ' +
                        (imagesUint8list
                                    .indexWhere((element) => element == image) +
                                1)
                            .toString(),
                    style: pw.TextStyle(fontSize: 22)),
                pw.SizedBox(height: 10),
                pw.Image(
                    pw.MemoryImage(
                      image,
                    ),
                    height: 400,
                    fit: pw.BoxFit.fitHeight)
              ]));
    }).toList();

    //create PDF
    pdf.addPage(pw.MultiPage(
        margin: pw.EdgeInsets.all(10),
        pageFormat: PdfPageFormat.a4,
        build: (pw.Context context) {
          return <pw.Widget>[
            pw.Column(
                crossAxisAlignment: pw.CrossAxisAlignment.center,
                mainAxisSize: pw.MainAxisSize.min,
                children: [
                  pw.Text('Create a Simple PDF',
                      textAlign: pw.TextAlign.center,
                      style: pw.TextStyle(fontSize: 26)),
                  pw.Divider(),
                ]),
            pw.Column(
                crossAxisAlignment: pw.CrossAxisAlignment.center,
                mainAxisSize: pw.MainAxisSize.max,
                children: pdfImages),
          ];
        }));
  }
```

One more step is to wrap the visibility widget with SingleChildScrollView and Expanded widgets.
``` dart
 Expanded(
              child: SingleChildScrollView(
                child: Visibility(
                  visible: pdfFile.isNotEmpty,
                  child: SfPdfViewer.file(File(pdfFile),
                      canShowScrollHead: false, canShowScrollStatus: false),
                ),
              ),
            ),
``` 

Now let's press the button one last time to create a PDF file with multiple images...

![pdf file flutter](https://media.giphy.com/media/NrNMICCs93MPjNRv2D/giphy.gif)


That's it!

Furthermore, I highly recommend checking the widget library of the PDF package in their official [API documentation.](https://pub.dev/documentation/pdf/latest/widgets/widgets-library.html)

You can [follow me on Twitter](https://twitter.com/Codereis1) and stay up to date with weekly Flutter tutorials.

Also, here is the [github repository](https://github.com/husbycodereis/pdf_create_view) for the source code of this project.