Flutter & WebView Communication: A Practical Guide with PDF.js Integration
Render and navigate PDF files with text selection support in Flutter using InAppWebView & PDF.js.
·Short read
Hello coders,
In today’s development world, creating communication between Flutter and web or JavaScript libraries can unlock powerful, interactive experiences for users.
One such example is integrating PDF.js, a powerful JavaScript library to display and handle PDFs.
We’ll explore how to set up two-way communication between Flutter & Flutter InAppWebView, and integrate PDF.js inside the Flutter environment to display and handle PDFs.
Load PDF from Local Storage:
Use file_picker to select a PDF and convert it to binary format for processing.
Render PDF with Text Selection:
Send the binary to PDF.js using flutter_inappwebview for rendering with text selection enabled.
Synchronize PDF Page Information:
Retrieve the total page count from PDF.js and pass it to Flutter when the PDF is visible.
Navigate and Jump to Specific Pages:
Send events from Flutter to PDF.js to navigate between or jump to specific pages, and retrieve the current page being rendered.
Let's start.
I am creating new project named - flutterwebpdf with flutter create flutterwebpdf .
here i am using the following packages,
Provider - for state management.
File Picker - to pick pdf files from storage.
Flutter InAppWebView - to create PDF.js web environment inside the flutter application.
So add them as dependencies inside your pubspec.yaml file.
now let's create the assets folder in your project root to keep all our PDF.js related dependencies and our webview html code and other javascript files.
here i am utlizing minified PDF.js↗ and pdf.worker.js↗ code to setup the pdf webview environment in flutter.
text5 lines
1 assets
2 ├── pdf.js
3 ├── pdf.worker.js
4 ├── pdfjs.html
5 └── script.js
Now let's create pdfjs.html↗ & script.js↗ to create PDF view, this script.js will take the pdf base64 data as input and to display it as WebView.
html82 lines
1<!-- pdfjs.html -->
2
3<!DOCTYPE html>
4<html lang="en">
5
6<head>
7 <meta charset="UTF-8">
8 <style>
9 body,
10 html {
11 margin: 0;
12 padding: 0;
13 height: 100%;
14 overflow: hidden;
15 background-color: rgb(1, 1, 15);
16 }
17
18 #pdf-container {
19 display: flex;
20 justify-content: center;
21 align-items: center;
22 height: 100vh;
23 width: 100vw;
24 box-sizing: border-box;
25 padding: 20px;
26 overflow: auto;
27 }
28
29 .pdf-page {
30 position: relative;
31 display: flex;
32 justify-content: center;
33 align-items: center;
34 margin: 0 auto;
35 }
36
37 canvas {
38 max-width: 100%;
39 height: auto;
40 box-shadow: 0 0 10px rgba(255, 255, 255, 0.1);
41 }
42
43 .textLayer {
44 position: absolute;
45 left: 0;
46 top: 0;
47 right: 0;
48 bottom: 0;
49 overflow: hidden;
50 opacity: 0.2;
51 line-height: 1.0;
52 user-select: text;
53 -webkit-user-select: text;
54 pointer-events: auto;
55 }
56
57 .textLayer>span {
58 color: transparent;
59 position: absolute;
60 white-space: pre;
61 cursor: text;
62 transform-origin: 0% 0%;
63 }
64
65 .textLayer ::selection {
66 background: rgba(0, 0, 255, 0.3);
67 }
68
69 </style>
70</head>
71
72<body>
73 <div id="pdf-container">
74 <div id="page-container" class="pdf-page">
75 <canvas id="pdf-canvas"></canvas>
76 </div>
77 </div>
78 <script src="pdf.js"></script>
79 <script src="script.js"></script>
80</body>
81
82</html>
this is the html template i am using with little css styling to fit to our Flutter Webview Environment.
css3 lines
1pdf-container
2 └── page-container (pdf-page)
3 └── pdf-canvas
pdf-container
This is the outermost container. It holds the entire PDF view.
page-container
This is a container inside pdf-container, which holds each individual PDF page.
pdf-canvas
This is a <canvas> element inside page-container where each page of the PDF will be rendered.
Now create script.js file inside assets folder to load the PDF.js library and render the PDF in HTML canvas by taking file base64 string as input.
69 console.log("Already on the requested page:", pageNum);
70 return;
71 }
72
73 currentPage = pageNum;
74 renderPage(currentPage).catch((error) => {
75 console.error("Error rendering page:", error);
76 });
77}
78
79function changePage(toNextPage) {
80 if (toNextPage && currentPage < pdfDoc.numPages) {
81 currentPage++;
82 renderPage(currentPage);
83 } else if (!toNextPage && currentPage > 1) {
84 currentPage--;
85 renderPage(currentPage);
86 }
87}
Okkk…
let me explain what this script.js file does.
Initialize PDF.js Worker: Sets the PDF.js worker source for background processing using pdfjsLib.GlobalWorkerOptions.workerSrc = "pdf.worker.js".
Define Variables:
container: The HTML element for PDF display.
pdfDoc: Holds the loaded PDF document object.
currentPage: Tracks the current page number (initialized to 1)
Load and Render PDF Document:
renderPdf(pdfBase64): Converts the Base64-encoded PDF (which is coming from the flutter) to a Uint8Array, then loads it with pdfjsLib.getDocument and assigns the result to pdfDoc.
Renders the first page using renderPage(currentPage).
Sends the total number of pages to Flutter using window.flutter_inappwebview.callHandler("totalPdfPages", pdfDoc.numPages). (This I will explain in a while).
Render Specific PDF Page
renderPage(pageNum): Retrieves and renders the specified page onto a canvas.
Sets the canvas dimensions to match the viewport, then renders the PDF page using page.render(renderContext).
Adds a selectable text layer overlay for content, and notifies Flutter of the current page using window.flutter_inappwebview.callHandler("currentPage", pageNum).
Jump to a Specific Page:
jumpToPage(pageNum): Jumps to a specified page if it's within bounds and different from currentPage.
Navigate Pages:
changePage(toNextPage): Adjusts currentPage up or down depending on toNextPage, then calls renderPage(currentPage) to display the updated page.
That's all we require the non-flutter setup for this project.
WebView Communication Overview
To send event from,
Flutter to WebView:
dart3 lines
1webViewController?.evaluateJavascript(
2 source: "callJsFunction($data)", // this source will be javascript code or function call
3);
this will execute the javascript functions that are declared in our script files.
WebView to Flutter
javascript3 lines
1// this will call the javascript handler
2// which we have attached when webview is loaded in Flutter
The app starts in main.dart, where MyApp sets up the root widget, loading HomePage as the initial screen and I am providing an instance of PdfProvider to the entire widget tree with MultiProvider, allowing any widget within the app to access and listen for changes to PdfProvider.
In HomePage, a Pick Pdf File button allows the user to select a PDF file. When selected, the file is converted to a base64 string for rendering and will save this base64 string in PdfProvider.
After selecting the PDF, the app navigates to PdfPage.
Here, an InAppWebView loads pdfjs.html from assets assets/which contains the JavaScript for rendering PDFs.
Once the HTML file loads will trigger the onWebViewLoaded method in PdfProvider by passing the InAppWebViewController as function parameter.
In bottom of PdfPage I've created a row of elements to,
Text Input: A TextField for entering a page number, which is used to jump to a specific page when submitted by triggering pdfProvider.jumpToPage(pageNo: pageNo ?? 1).
Page Indicator: Displays the current total number of pages in the PDF (/totalPages) when pdfProvider.pdfPagesCount changes .
IconButtons: Two IconButton s with a right & left arrow icon that allows the user to navigate to the next & previous page of the PDF by calling pdfProvider.changePage.
Let's breakdown the PdfProvider class and its functions.
selectPdfFile(): This will be trigger from HomePageOpens the file picker to select a PDF file & Converts the selected PDF file to a base64 string by calling convertBase64and stores it in selectedFileBase64. Returns true if a file is selected, false otherwise.
onWebViewLoaded(): This will get called when webview is loaded assets/pdfjs.html file in PdfPage InAppWebView on onLoadStop callback. this will execute the several functions and adds several WebView Listeners which are,
Execute renderPdf method in assets/script.js by passing selectedFileBase64 string, to load the PDF in WebView environment.
dart3 lines
1_webViewController?.evaluateJavascript(
2 source: "renderPdf('$selectedFileBase64')",
3);
**addTotalPdfPagesListener(): **Listens for page count changes triggered by window.flutter_inappwebview.callHandler("totalPdfPages", pdfDoc.numPages) in assets/script.js and updates pdfPagesCount in the provider.
**addCurrentPageListener(): **Listens for current page changes triggered by window.flutter_inappwebview.callHandler("currentPage", pageNum) in assets/script.js and updates currentPage in the provider and pdfPageController.text.
**jumpToPage(pageNo): **Executes jumpToPage($pageNo) JavaScript function in assets/script.js when TextField value is submitted in the PdfPage to navigate to a specific page in the PDF.
dart3 lines
1_webViewController?.evaluateJavascript(
2 source: "jumpToPage($pageNo)",
3);
**changePage(toNextPage): **Executes changePage($toNextPage) JavaScript function in assets/script.js to navigate to the next or previous page which will be triggered by left and right arrow buttons in PdfPage.
dart3 lines
1_webViewController?.evaluateJavascript(
2 source: "changePage($toNextPage)",
3);
That's IT… 😊
Final Output
That's it…… 🥳
Me and My Brother used this Idea to create a complete PDF viewer project as OpenPDF↗ with Flutter.
Please Check this post Link↗.
In this project we've added more features such as ,
Downloading PDFs, Offline Dictionary Support, Opened PDF Files history, and lot more features to represent a full fledged PDF Viewer Android App.