Flutter web - display pdf file generated in application. (Uint8List format)
Asked Answered
I

3

18

I am working on a iOS/Android/Web app where I am generating a PDF based on user input using the pdf plugin here: https://pub.dev/packages/pdf. On iOS and Android, I have access to the file system, so I am able to save the generated pdf first, and then open it with any pdf display plugin.

However, for Flutter web, as far as I know, there is no access to any type of file system. All the pdf render plugins that I've seen assume that the pdf file is either stored in a file, or on the web. I looked at:

  • flutter_full_pdf_viewer: ^1.0.6
  • flutter_pdf_viewer: ^0.6.1
  • pdf_render: ^0.57.2

How can I display this app-generated pdf on flutter web? Is there a way to spoof the Uint8List data as a file, or perhaps another way to approach this? (The only thing I can think of to solve this is by uploading the file to the web after generating it in order to display it, but it seems like overkill.)

Ibidem answered 22/4, 2020 at 19:40 Comment(0)
A
27

We can do it without additional plug-ins, because the web browser itself can display (or download) pdf document.

updated for pdf 2.0.0 and newer

Since method save now returns Future, button handlers become asynchronous (of course you can use FurureBuilder or something else).

import 'dart:html' as html;
import 'package:flutter/material.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;

class PdfLabPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final pdf = pw.Document();
    pdf.addPage(pw.Page(
        pageFormat: PdfPageFormat.a4,
        build: (pw.Context context) {
          return pw.Center(
            child: pw.Text('Hello World'),
          );
        }));

    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              onPressed: () async {
                final bytes = await pdf.save();
                final blob = html.Blob([bytes], 'application/pdf');
                final url = html.Url.createObjectUrlFromBlob(blob);
                html.window.open(url, '_blank');
                html.Url.revokeObjectUrl(url);
              },
              child: Text('Open'),
            ),
            ElevatedButton(
              onPressed: () async {
                final bytes = await pdf.save();
                final blob = html.Blob([bytes], 'application/pdf');
                final url = html.Url.createObjectUrlFromBlob(blob);
                final anchor = html.AnchorElement()
                  ..href = url
                  ..style.display = 'none'
                  ..download = 'some_name.pdf';
                html.document.body?.children.add(anchor);
                anchor.click();
                html.document.body?.children.remove(anchor);
                html.Url.revokeObjectUrl(url);
              },
              child: Text('Download'),
            ),
          ],
        ),
      ),
    );
  }
}

original answer

import 'package:flutter/material.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:universal_html/html.dart' as html;

class PdfDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final pdf = pw.Document();
    pdf.addPage(pw.Page(
        pageFormat: PdfPageFormat.a4,
        build: (pw.Context context) {
          return pw.Center(
            child: pw.Text("Hello World"),
          );
        }));
    final bytes = pdf.save();
    final blob = html.Blob([bytes], 'application/pdf');

    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          children: <Widget>[
            RaisedButton(
              child: Text("Open"),
              onPressed: () {
                final url = html.Url.createObjectUrlFromBlob(blob);
                html.window.open(url, "_blank");
                html.Url.revokeObjectUrl(url);
              },
            ),
            RaisedButton(
              child: Text("Download"),
              onPressed: () {
                final url = html.Url.createObjectUrlFromBlob(blob);
                final anchor =
                    html.document.createElement('a') as html.AnchorElement
                      ..href = url
                      ..style.display = 'none'
                      ..download = 'some_name.pdf';
                html.document.body.children.add(anchor);
                anchor.click();
                html.document.body.children.remove(anchor);
                html.Url.revokeObjectUrl(url);
              },
            ),
          ],
          mainAxisAlignment: MainAxisAlignment.center,
        ),
      ),
    );
  }
}
Addictive answered 23/4, 2020 at 19:58 Comment(9)
Thank you so much for writing this code. I've been trying to overcome this for a while, and it never occurred to me to use html. Bravo!Ibidem
I'm not familiar with HTML really, is there a way I can set the name of the PDF file when opening the PDF in browser instead of downloading? Right now it is getting set to some random string like "006f36b8-08b5-4530-9e7d-30a7a67e6d1e". Also, thank you very much for this solution! It works great.Nanji
For those of you receiving "Failed to load PDF document" make sure you are passing the correct blob. e.g. final url = html.Url.createObjectUrlFromBlob(html.Blob([pdfData.buffer]));Renie
final bytes = await pdf.save(); you need to add await pdf save to avoid corrupted pdfs and not loadings etcHolub
Thanks @iluvatar_GR, I've updated answer.Addictive
Any alternative to universal_html, getting hundreds of version solving failed messages, can't install any other package without deleting it first.Fermentation
On the example for Download is there a way to give the PDF a name? So that if they choose to share the file name doesn't show as Unknown.pdf ?Tombola
@Tombola look at download attribute. It should work on most recent browsers (with some implementation notes).Addictive
Amazing Man, Bravo, Download functionality works properly, but in case of pdf open in a new tab like ( Ctrl + p ) this is not working, and download a file. if i add a .pdf at the end of the downloaded file it is now a pdf. Any way to open this blob or bytes format PDF in flutter-web like ( Ctrl + p ).Bidding
S
7

Updated on Feb 12th, 2021:

After more rounds of googling and digging into documentation, I have got it to work. Here are the main points:

1- There is no need to insert any javascript code into <app_directory>/web/index.html file as I had pointed before. Sorry! I am learning flutter/dart...

2- I had to create a Future<html.Blob> method (please have a look at myGetBlobPdfContent() async {...} ) to deal with html/pdf content, to save (await pdf.save()) and to return a Blob value. I'm not the right person to say it was related with a "non complete future" was throwing an null point on pdf stuff...

3- Also, Those RaisedButton's onPressed methods were changed to async, to allow to get the pdf content as a Blob value (createObjectUrlFromBlob( await myGetBlobPdfContent());).

4- Finally, I was having warning messages: Helvetica has no Unicode support see https://github.com/DavBfr/dart_pdf/wiki/Fonts-Management, so that I have installed custom font like this cookbook instructions, in this example I have set up Arial ttf (data = await rootBundle.load("fonts/arial.ttf");) and those warning messages went away.

5- Here is my pubspec.yaml's snippet:

...
dependencies:
  flutter:
    sdk: flutter
  pdf: ^2.1.0
  universal_html: ^1.2.4

...

fonts:
  - family: Arial
    fonts:
      - asset: fonts/arial.ttf
      - asset: fonts/arialbd.ttf
        weight: 700
      - asset: fonts/ariali.ttf
        style: italic
  ...

6- And an minimal working example (full main.dart file):

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:universal_html/html.dart' as html;

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: PdfDemo(),
    );
  }
}

class PdfDemo extends StatelessWidget {
  Future<html.Blob> myGetBlobPdfContent() async {
    var data = await rootBundle.load("fonts/arial.ttf");
    var myFont = pw.Font.ttf(data);
    var myStyle = pw.TextStyle(font: myFont, fontSize: 36.0);

    final pdf = pw.Document();
    pdf.addPage(pw.Page(
        pageFormat: PdfPageFormat.a4,
        build: (pw.Context context) {
          return pw.Center(
            child: pw.Text(
              "Hello World",
              style: myStyle,
            ),
            // child: pw.Text("Hello World", style: myStyle),
          );
        }));
    final bytes = await pdf.save();
    html.Blob blob = html.Blob([bytes], 'application/pdf');
    return blob;
  }

  @override
  Widget build(BuildContext context) {
    myGetBlobPdfContent();
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          children: <Widget>[
            RaisedButton(
              child: Text("Open"),
              onPressed: () async {
                final url = html.Url.createObjectUrlFromBlob(
                    await myGetBlobPdfContent());
                html.window.open(url, "_blank");
                html.Url.revokeObjectUrl(url);
              },
            ),
            RaisedButton(
              child: Text("Download"),
              onPressed: () async {
                final url = html.Url.createObjectUrlFromBlob(
                    await myGetBlobPdfContent());
                final anchor =
                    html.document.createElement('a') as html.AnchorElement
                      ..href = url
                      ..style.display = 'none'
                      ..download = 'some_name.pdf';
                html.document.body.children.add(anchor);
                anchor.click();
                html.document.body.children.remove(anchor);
                html.Url.revokeObjectUrl(url);
              },
            ),
          ],
          mainAxisAlignment: MainAxisAlignment.center,
        ),
      ),
    );
  }
}

7- At the end, the PDF generation (and download) is working as expected:

enter image description here

Hope this helps.

Initial post (Only to keep the information log)

@Spatz's answer might solve any pdf issue for web platform. I don't know why it is not working as expected, at least for my setup (see below).

I mean the webpage tries to open the pdf and throws an error: Failed to load PDF document.

enter image description here

Also, the example downloads a pdf file (some_name.pdf) successfully, but it fails when I try to open the pdf file (error: file corrupted).

I have seen that this post talks about insert javascript code into <app_directory>/web/index.html file. I have insert those lines and no success as well.

The source code I am using is a one main.dart file:

import 'package:flutter/material.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:universal_html/html.dart' as html;

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: PdfDemo(),
    );
  }
}

class PdfDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final pdf = pw.Document();
    pdf.addPage(pw.Page(
        pageFormat: PdfPageFormat.a4,
        build: (pw.Context context) {
          return pw.Center(
            child: pw.Text("Hello World"),
          );
        }));
    final bytes = pdf.save();
    final blob = html.Blob([bytes], 'application/pdf');

    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          children: <Widget>[
            RaisedButton(
              child: Text("Open"),
              onPressed: () {
                final url = html.Url.createObjectUrlFromBlob(blob);
                html.window.open(url, "_blank");
                html.Url.revokeObjectUrl(url);
              },
            ),
            RaisedButton(
              child: Text("Download"),
              onPressed: () {
                final url = html.Url.createObjectUrlFromBlob(blob);
                final anchor =
                    html.document.createElement('a') as html.AnchorElement
                      ..href = url
                      ..style.display = 'none'
                      ..download = 'some_name.pdf';
                html.document.body.children.add(anchor);
                anchor.click();
                html.document.body.children.remove(anchor);
                html.Url.revokeObjectUrl(url);
              },
            ),
          ],
          mainAxisAlignment: MainAxisAlignment.center,
        ),
      ),
    );
  }
}

My setup is:

  • Flutter (Channel beta, 1.26.0-17.3.pre, on Microsoft Windows [Version 10.0.19042.746], locale en-150)
  • Android tool chain develop for Android devices (Android SDK version 30.0.3)
  • Chrome - develop for the web
  • Android Studio (version 4.1.0)

Thank you :)

Salutation answered 10/2, 2021 at 15:30 Comment(0)
D
1

In order to resolve "Failed to open PDF file"... which is due to invalid format of the file... add the following two lines to the existing code:

 var  data = await pdf.save();
 Uint8List bytes = Uint8List.fromList(data);

The complete code will be as below:

  var  data = await pdf.save();
    Uint8List bytes = Uint8List.fromList(data);
    final blob = html.Blob([bytes], 'application/pdf');
    final url = html.Url.createObjectUrlFromBlob(blob);
    final anchor =
    html.document.createElement('a') as html.AnchorElement
      ..href = url
      ..style.display = 'none'
      ..download = 'some_name.pdf';

    html.document.body.children.add(anchor);
    anchor.click();
    html.document.body.children.remove(anchor);
    html.Url.revokeObjectUrl(url);
Dissidence answered 3/8, 2021 at 10:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.