PDF File generation with Flutter web - flutter

I'm in the process of Web'ifing a Flutter mobile application and attempting to get around the lack for support for Path Provider in Web.
I'm using the PDF (pdf: ^1.9.0) package to generate a PDF document and upload it to Google Drive and I'm trying to find out if I can generate and store the PDF In memory to make it web compatible.
Example current code using Path Provider.
createFile() async {
final downloads = await getApplicationSupportDirectory();
final file = File("${downloads.path}/$filename.pdf");
await file.writeAsBytes(pdf.save());
await GoogleDriveController.uploadFileToGoogleDrive(file.path);
}
Question: Is there a way to generate and store Fies in memory for web using Flutter web?

I managed to find a work around to generate the PDF and trigger a download via the browser instead and thought I should post incase anyone stumbles across this.
//Create PDF in Bytes
Uint8List pdfInBytes = pdf.save();
//Create blob and link from bytes
final blob = html.Blob([pdfInBytes], 'application/pdf');
final url = html.Url.createObjectUrlFromBlob(blob);
final anchor = html.document.createElement('a') as html.AnchorElement
..href = url
..style.display = 'none'
..download = 'pdf.pdf';
html.document.body.children.add(anchor);
//Trigger the download of this PDF in the browser.
RaisedButton(
child: Text('Press'),
onPressed: () {
anchor.click();
Navigator.pop(context);
},
)

My answer is a variant on Yonkee above specifically for flutter web. In this answer, I have added the imports required (dart:html and dart:typed_data) and added formatting of text as I needed that feature.
import 'package:flutter/material.dart';
import 'package:pdf/widgets.dart' as pw;
import 'dart:typed_data';
import 'dart:html' as html;
class PDFSave extends StatefulWidget {
#override
_PDFSaveState createState() => _PDFSaveState();
}
class _PDFSaveState extends State<PDFSave> {
final pdf = pw.Document();
var anchor;
savePDF() async {
Uint8List pdfInBytes = await pdf.save();
final blob = html.Blob([pdfInBytes], 'application/pdf');
final url = html.Url.createObjectUrlFromBlob(blob);
anchor = html.document.createElement('a') as html.AnchorElement
..href = url
..style.display = 'none'
..download = 'pdf.pdf';
html.document.body.children.add(anchor);
}
createPDF() async {
pdf.addPage(
pw.Page(
build: (pw.Context context) => pw.Column(
children: [
pw.Text('Hello World', style: pw.TextStyle(fontSize: 40)),
],
),
),
);
savePDF();
}
#override
void initState() {
super.initState();
createPDF();
}
#override
Widget build(BuildContext context) {
return Scaffold(backgroundColor: Colors.transparent,
appBar: AppBar(
title: Text('PDF Creator'),
),
body: Center(
child:RaisedButton(
child: Text('Press'),
onPressed: () {
anchor.click();
Navigator.pop(context);
},
)
));
}
}

Related

How do I create a "fake" dart:io File from MemoryFileSystem bytes?

I have the in-memory bytes of a decrypt epub file, but the package that I want to open this epub file with only accepts dart:io File objects.
I don't want to create real local file => just for security
Is there a way to create a "fake" dart:io File, simply wrapping my in-memory bytes, so that I can pass this "fake" File to the package?
I'm using vocsy_epub_viewer package to open epub files
filePath should be a local file
import 'package:archive/archive.dart';
import 'package:flutter/material.dart';
import 'package:vocsy_epub_viewer/epub_viewer.dart';
import 'package:file_picker/file_picker.dart';
import 'package:path_provider/path_provider.dart';
import 'package:encrypt/encrypt.dart' as en;
import 'dart:io';
import 'package:cross_file/cross_file.dart';
import 'package:file/memory.dart';
import 'package:file/file.dart' as F;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Ebook(),
);
}
}
class Ebook extends StatefulWidget {
#override
State<Ebook> createState() => _EbookState();
}
class _EbookState extends State<Ebook> {
Directory? _temp;
String? path;
#override
void initState() {
address();
super.initState();
}
Future decodeEpub(String path) async {
final bytes = await File(path).readAsBytes();
Archive archive =
await ZipDecoder().decodeBytes(bytes, password: '7web', verify: true);
final data = await archive.first.content;
final key = en.Key.fromUtf8('qwertyuiopasdfghjklmnbvcxzasdfgh');
final iv = en.IV.fromLength(16);
final decrypted = await en.AES(key).decrypt(en.Encrypted(data), iv: iv);
// XFile file = await XFile.fromData(decrypted,name:'2.epub',path:"${_appDocumentsDirectory!.path}/2.epub" );
// File file1= await File(file.path).create(recursive: true);
// File file = MemoryFileSystem().file('${temp!.path}/2.epub')..create(recursive: true)
// ..writeAsBytesSync(decrypted);
File file = await File('${_temp!.path}/2.epub').writeAsBytes(decrypted); // it must be change ******
print(file.path);
return file.path;
}
void address() async {
final temp = await getTemporaryDirectory();
setState(() {
_temp = temp;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () async {
FilePickerResult? result =
await FilePicker.platform.pickFiles();
if (result != null) {
File file = File(result.files.single.path!);
path = await decodeEpub(file.path);
print('?= $path , ${file.path}');
} else {
print('cancel');
}
},
child: Text('add File')),
ElevatedButton(
onPressed: () {
EpubViewer.setConfig(
themeColor: Theme.of(context).primaryColor,
identifier: "iosBook",
scrollDirection: EpubScrollDirection.ALLDIRECTIONS,
allowSharing: true,
enableTts: true,
nightMode: false);
EpubViewer.open(path!, lastLocation: EpubLocator());
},
child: Text("open"),
),
],
),
),
);
}
}
I don't want to create real local file => just for security
This is not actually true because EPUBs are structured as a zip file and folioreader, the framework used behind the vocsy_epub_viewer, will "unzip" the file to a temp folder (or one predefined). So, in the end, it's not going to be secure anyway.
Just extract the EPUB to the temp folder from path_provider and remove it afterwards. Otherwise, you're going to need a new framework to read EPUBs that don't have this behaviour or even customize the vocsy_epub_viewer/folioreader sources yourself to make it secure.

How to load image from assets folder inside a pdf in Flutter web?

We want to show image on a pdf from assets folder in Flutter web application:
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:flutter/material.dart';
.............
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: Container(
height: 400,
width: 900,
child: PdfPreview(
build: (format) => _generatePdf(format, "SOME TITLE"),
)),
),
]));
}
Future<Uint8List> _generatePdf(PdfPageFormat format) async {
final pdf = pw.Document();
pdf.addPage(
pw.Page(
pageFormat: format,
build: (context) {
return pw.Image(AssetImage('assets/imglogo.png')); //This line gives the error
}));
return pdf.save();
}
This code gives error:
The argument type 'AssetImage' can't be assigned to the parameter type 'ImageProvider'
The documentation addresses only two cases To load an image from a file:(dart.io is not supported on the web), and To load an image from the network using the printing package:, which is not the case, so we tried the solutions provided here: 1,2, but each one gives a different exception.
Is there another approach to achieve this?
You can convert your ByteData directly to Uint8List as shown in the example code below. This can then be passed to the MemoryImage constructor:
Future<void> addPage(pw.Document pdf, String filename) async {
final imageByteData = await rootBundle.load('assets/$filename');
// Convert ByteData to Uint8List
final imageUint8List = imageByteData.buffer
.asUint8List(imageByteData.offsetInBytes, imageByteData.lengthInBytes);
final image = pw.MemoryImage(imageUint8List);
pdf.addPage(
pw.Page(
build: (pw.Context context) {
return pw.Center(
child: pw.Image(image),
); // Center
},
),
);
}
To achieve this you can get the asset image as a file, and then use this file in the PDF. If I take your code, we can add a function to get a File representation of your asset image :
Future<File> getImageFileFromAssets(String path) async {
final byteData = await rootBundle.load('assets/$path');
final file = File('${(await getTemporaryDirectory()).path}/$path');
await file.writeAsBytes(byteData.buffer
.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes));
return file;
}
To make it works, you need to add path_provider to your dependencies.
Then you can use this function in your pdf generation function :
final pdf = pw.Document();
final file = await getImageFileFromAssets(yourpath);
final image = pw.MemoryImage(
file.readAsBytesSync(),
);
pdf.addPage(pw.Page(
pageFormat: format,
build: (context) {
return pw.Image(image);
}));
return pdf.save();

How to solve Unhandled Exception: FormatException: Could not find End of Central Directory Record while downloading file in Flutter?

In my Flutter project, I want to download some files as zip and then unzip it programmatically and save it in device locally. So, for that reason I followed some examples, here's the code for that-
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:archive/archive.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
class DownloadAssetsDemo extends StatefulWidget {
DownloadAssetsDemo() : super();
final String title = "Download & Extract ZIP Demo";
#override
DownloadAssetsDemoState createState() => DownloadAssetsDemoState();
}
class DownloadAssetsDemoState extends State<DownloadAssetsDemo> {
//
bool _downloading;
String _dir;
List<String> _images, _tempImages;
String _zipPath = 'https://coderzheaven.com/youtube_flutter/images.zip';
String _localZipFileName = 'images.zip';
#override
void initState() {
super.initState();
_images = List();
_tempImages = List();
_downloading = false;
_initDir();
}
_initDir() async {
if (null == _dir) {
_dir = (await getApplicationDocumentsDirectory()).path;
}
}
Future<File> _downloadFile(String url, String fileName) async {
var req = await http.Client().get(Uri.parse(url));
var file = File('$_dir/$fileName');
return file.writeAsBytes(req.bodyBytes);
}
Future<void> _downloadZip() async {
setState(() {
_downloading = true;
});
_images.clear();
_tempImages.clear();
var zippedFile = await _downloadFile(_zipPath, _localZipFileName);
await unarchiveAndSave(zippedFile);
setState(() {
_images.addAll(_tempImages);
_downloading = false;
});
}
unarchiveAndSave(var zippedFile) async {
var bytes = zippedFile.readAsBytesSync();
var archive = ZipDecoder().decodeBytes(bytes);
for (var file in archive) {
var fileName = '$_dir/${file.name}';
if (file.isFile) {
var outFile = File(fileName);
//print('File:: ' + outFile.path);
_tempImages.add(outFile.path);
outFile = await outFile.create(recursive: true);
await outFile.writeAsBytes(file.content);
}
}
}
buildList() {
return Expanded(
child: ListView.builder(
itemCount: _images.length,
itemBuilder: (BuildContext context, int index) {
return Image.file(
File(_images[index]),
fit: BoxFit.fitWidth,
);
},
),
);
}
progress() {
return Container(
width: 25,
height: 25,
padding: EdgeInsets.fromLTRB(0.0, 20.0, 10.0, 20.0),
child: CircularProgressIndicator(
strokeWidth: 3.0,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: <Widget>[
_downloading ? progress() : Container(),
IconButton(
icon: Icon(Icons.file_download),
onPressed: () {
_downloadZip();
},
),
],
),
body: Container(
child: Column(
children: <Widget>[
buildList(),
],
),
),
);
}
}
This example, works fine with all the functionalities- zip file download, extract the file and load the images.
But the problem is
When I want to download the file from my desired location where I have saved a sqlite database(Size:19 mb) as a zip file, it doesn't work like the way it happened for the given code.
It shows the following error exception-
[ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: FormatException: Could not find End of Central Directory Record
And I am not exactly getting whether the problem is in my download path or I need to make some changes in my coding example?
So, I need some suggestion to fix this exception and download and unzip my desired file from desired url.
This was likely caused by the fact that the file was not yet flushed to the filesystem after downloading before attempting to extract same.
To fix this update the _downloadFile method to the following
Future<File> _downloadFile(String url, String fileName) async {
var req = await http.Client().get(Uri.parse(url));
var file = File('$_dir/$fileName');
return file.writeAsBytes(req.bodyBytes, flush: true); // Added flush: true
}
From dart:io docs
Future<File> writeAsBytes(List<int> bytes, {FileMode mode = FileMode.write, bool flush = false})
Writes a list of bytes to a file.
Opens the file, writes the list of bytes to it, and closes the file. Returns a Future<File> that completes with this [File] object once the entire operation has completed.
By default [writeAsBytes] creates the file for writing and truncates the file if it already exists. In order to append the bytes to an existing file, pass [FileMode.append] as the optional mode parameter.
Note: --> If the argument [flush] is set to true, the data written will be flushed to the file system before the returned future completes.

Flutter downloads partial files from Google Drive and One drive

Flutter can not download files from Google Drive or OneDrive, zip or otherwise.
The files just get partially downloaded whereas files from most other links from websites etc are downloaded completely.
For example this Google Drive link will only get 10% file download but the other URL for a novel will get 100%
Packages used:
http: ^0.12.2
dio: ^3.0.10
path_provider: ^1.6.24
Google Drive URL = 'https://drive.google.com/file/d/1xnhT8mzMeU-wRemt1sNFR0DJt2MNmSC8/view?usp=sharing';
Gutenberg Novel URL = 'https://www.gutenberg.org/files/1342/1342-h.zip';
Example code shows both links and result.
Output:
Expected size: myColorsDIO.zip: 594,545 , myColorsHTTP.zip: 594,545 ,
1342DIO.zip: 778512, 1342HTTP.zip: 778512
Obtained size:
myColorsDIO.zip: 63411, myColorsHTTP.zip: 70677, 1342DIO.zip: 778512,
1342HTTP.zip: 778512
Please help.
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'dart:io';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(title: 'Flutter Demo', theme: ThemeData(primarySwatch: Colors.blue,visualDensity: VisualDensity.adaptivePlatformDensity,), home: MyHomePage(),);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Downloading Files'),),
body: Center(child: Text('Press the floating button to download files',),),
floatingActionButton: FloatingActionButton(
onPressed: () { _downloadFile();},
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
Future<File> _downloadFile() async {
String myUrl = 'https://drive.google.com/file/d/1xnhT8mzMeU-wRemt1sNFR0DJt2MNmSC8/view?usp=sharing';
String my7ZIPUrl = 'https://www.gutenberg.org/files/1342/1342-h.zip';
String myDownloadDirectory = ((await getApplicationDocumentsDirectory()).path);
String myDownloadedGoogleFile1 = 'myColorsDIO.zip';
String myDownloadedGoogleFile2 = 'myColorsHTTP.zip';
String myDownloadedfromgutenberg1 = '1342DIO.zip';
String myDownloadedfromgutenberg2 = '1342HTTP.zip';
print('Download Directory: $myDownloadDirectory');
//Downloading Google Drive zip file via DIO
Dio dio = Dio();
await dio.download(
myUrl,
'$myDownloadDirectory/$myDownloadedGoogleFile1',
onReceiveProgress: (rcv, total) {print('received: ${rcv.toStringAsFixed(0)} out of total: ${total.toStringAsFixed(0)}');}
);
await dio.download(
my7ZIPUrl,
'$myDownloadDirectory/$myDownloadedfromgutenberg1',
onReceiveProgress: (rcv, total) {print('received: ${rcv.toStringAsFixed(0)} out of total: ${total.toStringAsFixed(0)}');}
);
//Downloading Google Drive zip file via HTTP
var req = await http.Client().get(Uri.parse(myUrl));
var file = File('$myDownloadDirectory/$myDownloadedGoogleFile2',);
file.writeAsBytes(req.bodyBytes);
//Downloading Google Drive zip file via HTTP
var req1 = await http.Client().get(Uri.parse(my7ZIPUrl));
var file1 = File('$myDownloadDirectory/$myDownloadedfromgutenberg2',);
file1.writeAsBytes(req1.bodyBytes);
print('Expected size: myColorsDIO.zip: 594,545 , myColorsHTTP.zip: 594,545 , 1342DIO.zip: 778512, 1342HTTP.zip: 778512');
print('myColorsDIO.zip: ${await File('$myDownloadDirectory/$myDownloadedGoogleFile1',).length()}, myColorsHTTP.zip: ${await File('$myDownloadDirectory/$myDownloadedGoogleFile2',).length()}, 1342DIO.zip: ${await File('$myDownloadDirectory/$myDownloadedfromgutenberg1',).length()}, 1342HTTP.zip: ${await File('$myDownloadDirectory/$myDownloadedfromgutenberg2',).length()}');
return null;
}
}
Sharing link does not work directly, put it in the browser and when you are prompted for a download, that is the URL that works.

flutter download an Image from url

I'm trying to load image from server using networkimage() and I want to download the same once it is loaded.. can anyone suggest me some ideas.
CircleAvatar(
backgroundImage: NetworkImage(url),
maxRadius: 15.0,
);
Here I'm loading image from my server. I want to save to the image to particular path after the image is loaded.
I recently battled this, and decided to solve it without plugins. I hope it helps someone.
The below program downloads a picture from the web, stores it in the device's local path, and then displays it when run. (note, it does not work for flutter web because you don't have access to the local file storage on that platform. Instead you would have to save the image to a local database using a plugin like sqflite, or hive from pub.dev.) Here's the code:
import 'package:flutter/material.dart';
import 'package:http/http.dart' show get;
import 'dart:io';
import 'package:path_provider/path_provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test Image',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Test Image'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
initState() {
_asyncMethod();
super.initState();
}
_asyncMethod() async {
//comment out the next two lines to prevent the device from getting
// the image from the web in order to prove that the picture is
// coming from the device instead of the web.
var url = "https://www.tottus.cl/static/img/productos/20104355_2.jpg"; // <-- 1
var response = await get(url); // <--2
var documentDirectory = await getApplicationDocumentsDirectory();
var firstPath = documentDirectory.path + "/images";
var filePathAndName = documentDirectory.path + '/images/pic.jpg';
//comment out the next three lines to prevent the image from being saved
//to the device to show that it's coming from the internet
await Directory(firstPath).create(recursive: true); // <-- 1
File file2 = new File(filePathAndName); // <-- 2
file2.writeAsBytesSync(response.bodyBytes); // <-- 3
setState(() {
imageData = filePathAndName;
dataLoaded = true;
});
}
String imageData;
bool dataLoaded = false;
#override
Widget build(BuildContext context) {
if (dataLoaded) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.file(File(imageData), width: 600.0, height: 290.0)
],
),
),
);
} else {
return CircularProgressIndicator(
backgroundColor: Colors.cyan,
strokeWidth: 5,
);
}
}
}
pubspec.yaml file:
http: ^0.12.1
path_provider: ^1.6.5
flutter version: 1.20.0-3.0.pre.112
dart version 2.9.0-19.0.dev
I recommend image_downloader.
For ios, image is saved in Photo Library.
For Android, image is saved in Environment.DIRECTORY_DOWNLOADS or specified location. By calling inExternalFilesDir(), specification of permission becomes unnecessary.
By callback(), you can get progress status.
The following is the simplest example. It will be saved.
await ImageDownloader.downloadImage(url);
I used image_downloader.
Use await ImageDownloader.downloadImage("url") of image_downloader package's method to download image using it's url.
Note : above method will return value as follows :-
imageId of the saved image if saving succeeded.
null if not been granted permission.
for this you have to ask for storage permission, just add following line into android manifest file :
uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
Otherwise it is a PlatformException.
I tried many solution, but this is simplest solution for my... Just try it
STEP - 1
Add this package in your pubspec.yaml file
dependencies:
image_downloader: ^0.20.1
STEP - 2
Add this in your dart file
import 'package:image_downloader/image_downloader.dart';
STEP - 3
Write this code on press download button
ColButton(
title: 'Download',
icon: Icons.file_download,
onTap: () async {
try {
showLoadingDialog(context);
// Saved with this method.
var imageId =
await ImageDownloader.downloadImage("https://raw.githubusercontent.com/wiki/ko2ic/image_downloader/images/bigsize.jpg");
if (imageId == null) {
return;
}
// Below is a method of obtaining saved image information.
var fileName = await ImageDownloader.findName(imageId);
var path = await ImageDownloader.findPath(imageId);
var size = await ImageDownloader.findByteSize(imageId);
var mimeType = await ImageDownloader.findMimeType(imageId);
Navigator.pop(context);
showToast('Image downloaded.');
} on PlatformException catch (error) {
print(error);
}
},
),
I use this plugin to save image in the phone using an URL
https://pub.dartlang.org/packages/image_picker_saver
For more advanced handling of Image/File downloads, you can consider the flutter_downloader package.
Some of the features that I like are :
Shows OS level download progress
can track all downloads
Has notification