I have a table in postgresql that has a field with a pdf file in base64 format. When I get it from flutter, how can I show it with pdf viewer ?, has anyone done it ??
I appreciate your help
In pubspec.yaml add the following dependency:
dependencies:
flutter_full_pdf_viewer: ^1.0.6 # for pdf
Then run flutter pub get in Android Studio terminal to update dependencies.
Create the file 'pdfBase64Viewer.dart:
import 'dart:async'; // asynchroneous function (await)
import 'dart:io'; // write the file on user's phone
import 'dart:convert'; // handle base64 decoding
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_full_pdf_viewer/full_pdf_viewer_scaffold.dart';
import 'package:path_provider/path_provider.dart';
class PdfBase64Viewer extends StatefulWidget {
final String url;
const PdfBase64Viewer(String this.url);
#override
_PdfBase64ViewerState createState() => new _PdfBase64ViewerState();
}
class _PdfBase64ViewerState extends State<PdfBase64Viewer> {
String pathPDF = "";
#override
void initState() {
super.initState();
createFileOfPdfUrl(widget.url).then((f) {
setState(() {
pathPDF = f.path;
print(pathPDF);
});
});
}
Future<File> createFileOfPdfUrl(is_base_64) async {
final filename = widget.url.substring(widget.url.lastIndexOf("/") + 1);
var request = await HttpClient().getUrl(Uri.parse(widget.url));
var response = await request.close();
var bytes = await consolidateHttpClientResponseBytes(response);
var base64String = utf8.decode(bytes);
var decodedBytes = base64Decode(base64String.replaceAll('\n', ''));
String dir = (await getApplicationDocumentsDirectory()).path;
File file = new File('$dir/$filename');
await file.writeAsBytes(decodedBytes.buffer.asUint8List());
return file;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Plugin example app')),
body: Center(
child: RaisedButton(
child: Text("Open PDF"),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => PDFScreen(pathPDF)),
),
),
),
);
}
}
class PDFScreen extends StatelessWidget {
String pathPDF = "";
PDFScreen(this.pathPDF);
#override
Widget build(BuildContext context) {
return PDFViewerScaffold(
appBar: AppBar(
title: Text("Document"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.share),
onPressed: () {},
),
],
),
path: pathPDF);
}
}
You can adapt above class in order to assign to the variable base64String the String value that you get from your DB.
You can use it the following way:
import 'pdfBase64Viewer.dart';
(...)
child: PdfBase64Viewer("https://filebin.net/qmvmxbgn6pzgkbzb/sample.base64?t=7g5s9rwr") // url of your base64 file
(...)
You might want to check the following documentation:
Base64
Full Pdf Viewer
Related
I am working on an app that displays pdf from URL(cloud firestore) that is passed from the previous screen to the pdf screen. I want every time the user loads the URL, the pdf should be cached so that no loading is required next time. I tried using cached_pdf_view it works but it is missing features and it is giving me problems when scrolling the pdf. I want to use advance_pdf_viewer or syncfusion pdf package to display the pdf and cache it. I got some help from https://www.syncfusion.com/forums/165812/cache-pdf-from-network on the package and replaced my code but am getting this new error FileSystemException: Cannot open file, path . This is my pdf screen.
class PdfScreen extends StatefulWidget {
final String url;
final String id;
const PdfScreen({super.key, required this.url, required this.id});
#override
_HomePage createState() => _HomePage();
}
class _HomePage extends State<PdfScreen> {
final GlobalKey<SfPdfViewerState> _pdfViewerKey = GlobalKey();
File? _tempFile;
#override
void initState() {
initializeFile();
super.initState();
}
//ignore: avoid_void_async
void initializeFile() async {
final Directory tempPath = await getApplicationDocumentsDirectory();
final File tempFile = File('${tempPath.path}/${widget.url}');
final bool checkFileExist = await tempFile.exists();
if (checkFileExist) {
_tempFile = tempFile;
}
}
#override
Widget build(BuildContext context) {
Widget child;
if (_tempFile == null) {
print('no temp fille');
child = SfPdfViewer.network(widget.url, key: _pdfViewerKey,
onDocumentLoaded: (PdfDocumentLoadedDetails details) async {
final Directory tempPath = await getApplicationDocumentsDirectory();
_tempFile = await File('${tempPath.path}/${widget.url}')
.writeAsBytes(List.from(await details.document.save()));
});
} else {
child = SfPdfViewer.file(
_tempFile!,
key: _pdfViewerKey,
);
}
return Scaffold(
appBar: AppBar(
title: const Text('Syncfusion Flutter PDF Viewer'),
actions: <Widget>[
IconButton(
icon: const Icon(
Icons.bookmark,
color: Colors.white,
),
onPressed: () {
_pdfViewerKey.currentState?.openBookmarkView();
},
),
],
),
body: child,
);
`` }
` `}
I tried share package and it works but I want to add a Widget that only appears on the shared image, not on the app. So please help me this is the code so far:
import 'dart:ui';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:share/share.dart';
void main() {
runApp(MaterialApp(home: HomePage()));
}
class HomePage extends StatelessWidget {
final _contentKey = GlobalKey();
final bool forSharing;
HomePage({
Key key,
this.forSharing = false,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: const Icon(Icons.share),
onPressed: () => onTakeScreenShotAndSharing(
'Image',
_contentKey.currentContext,
),
),
],
),
body: RepaintBoundary(
key: _contentKey,
child: Column(
children: [
if (forSharing) Text('Text appear only for Sharing'), // This is the widget that I want it appear only on the shared image
Text('Content'),
],
),
),
);
}
void onTakeScreenShotAndSharing(String fileName, BuildContext context) async {
try {
RenderRepaintBoundary boundary = context.findRenderObject();
final image = await boundary?.toImage();
final bytes = await image?.toByteData(format: ImageByteFormat.png);
if (bytes == null || bytes.buffer == null) return;
final dir = (await getApplicationDocumentsDirectory()).path;
final file = File('$dir/$fileName${DateTime.now().toString()}.png');
final newFile = await file.writeAsBytes(bytes.buffer.asUint8List());
await Share.shareFiles([newFile.path]);
} catch (error) {
print(error);
}
}
}
I am very new to flutter and coding. This may be an easy fix but I cannot find a solution anywhere or reconfigure the code to display the name of the file I am picking.
I am trying to create a web page that can accept document submissions. However, I cannot get the code to display the name of the file picked.
The print call is working, however, the fileNameSelected does not update.
Thank you for any help!!
import 'dart:html';
import 'dart:io' as io; //type cast
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:file_picker/file_picker.dart';
import 'package:path/path.dart';
import './appbar/appbar.dart';
class UploadFPage extends StatefulWidget {
const UploadFPage({Key? key}) : super(key: key);
#override
State<UploadFPage> createState() => _UploadFPageState();
}
class _UploadFPageState extends State<UploadFPage> {
UploadTask? task;
File? file;
String fileNameSelected = 'No File Selected';
#override
Widget build(BuildContext context) {
final fileName = fileNameSelected;
return Scaffold(
appBar: AppBar(
title: const Text('AppBar'),
),
body: ListView(
children: [
Container(
height: 300,
color: Colors.amber,
child: Row(
children: [
ElevatedButton.icon(
icon: const Icon(Icons.attach_file, size: 28),
onPressed: selectFile,
label: Text("Attach Invoice"),
),
Text(fileName),
],
),
),
],
),
);
}
Future selectFile() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowMultiple: false,
allowedExtensions: ['pdf']);
if (result == null) {
return;
} else {
print(result.files.single.name);
//final name = result.files.single.name;
//final path = result.files.single.path!;
//final file = result.files.single.bytes!;
setState(() {
String fileNameSelected = basename(result.files.single.name);
//file = File(size, path);
});
}
}
}
Change:
setState(() {
String fileNameSelected = basename(result.files.single.name);
});
to
setState(() {
fileNameSelected = basename(result.files.single.name);
});
By adding var, final or String in front you are declaring a new variable (which happens to have the same name as the one you declared above). You assign your picked file to that - and it promptly goes out of scope.
I'm brand new to Flutter and am struggling to display a PDF from a Base64 string. I can get it to work, but only with a pointless UI sitting between the list and the PDF.
The app simply crashes with 'Lost connection to device' and no other information appears in the Android Studio debug console.
So, this works:
import 'dart:developer';
import 'dart:async';
import 'dart:io';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_full_pdf_viewer/full_pdf_viewer_scaffold.dart';
import 'package:path_provider/path_provider.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class PdfBase64Viewer extends StatefulWidget {
final DocumentSnapshot document;
const PdfBase64Viewer(this.document);
#override
_PdfBase64ViewerState createState() => new _PdfBase64ViewerState();
}
class _PdfBase64ViewerState extends State<PdfBase64Viewer> {
String pathPDF = "";
#override
void initState() {
super.initState();
createFile().then((f) {
setState(() {
pathPDF = f.path;
print("Local path: " + pathPDF);
});
});
}
Future<File> createFile() async {
final filename = widget.document.data()['name'];
var base64String = widget.document.data()['base64'];
var decodedBytes = base64Decode(base64String.replaceAll('\n', ''));
String dir = (await getApplicationDocumentsDirectory()).path;
File file = new File('$dir/$filename');
await file.writeAsBytes(decodedBytes.buffer.asUint8List());
return file;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('This UI is pointless')),
body: Center(
child: RaisedButton(
child: Text("Open PDF"),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => PDFScreen(pathPDF)),
),
),
),
);
}
}
class PDFScreen extends StatelessWidget {
String pathPDF = "";
PDFScreen(this.pathPDF);
#override
Widget build(BuildContext context) {
return PDFViewerScaffold(
appBar: AppBar(
title: Text("Document"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.share),
onPressed: () {},
),
],
),
path: pathPDF);
}
}
But this doesn't:
import 'dart:developer';
import 'dart:async';
import 'dart:io';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_full_pdf_viewer/full_pdf_viewer_scaffold.dart';
import 'package:path_provider/path_provider.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class PdfBase64Viewer extends StatefulWidget {
final DocumentSnapshot document;
const PdfBase64Viewer(this.document);
#override
_PdfBase64ViewerState createState() => new _PdfBase64ViewerState();
}
class _PdfBase64ViewerState extends State<PdfBase64Viewer> {
String pathPDF = "";
#override
void initState() {
super.initState();
createFile().then((f) {
setState(() {
pathPDF = f.path;
print("Local path: " + pathPDF);
});
});
}
Future<File> createFile() async {
final filename = widget.document.data()['name'];
var base64String = widget.document.data()['base64'];
var decodedBytes = base64Decode(base64String.replaceAll('\n', ''));
String dir = (await getApplicationDocumentsDirectory()).path;
File file = new File('$dir/$filename');
await file.writeAsBytes(decodedBytes.buffer.asUint8List());
return file;
}
#override
Widget build(BuildContext context) {
return PDFViewerScaffold(
appBar: AppBar(
title: Text("Document"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.share),
onPressed: () {},
),
],
),
path: pathPDF);
}
}
Can anyone help me understand why?
You should pay more attention to what the output is (or isn't), it will make your debugging process a lot faster and without needing SO most of the time.
The app simply crashes with 'Lost connection to device' and no other
information appears in the Android Studio debug console.
So that means you aren't seeing print("Local path: " + pathPDF); Which means your app hasn't made it that far.
As to why it is crashing, welp without any testing I can see you aren't handling your asyncs and futures properly. You are just assuming they will be ok and you are assuming they will finish quickly (before the widget gets built). Your PDFViewerScaffold is most probably getting an empty string. A simple check can fix that:
#override
Widget build(BuildContext context) {
return (pathPDF == null || pathPDF.isEmpty) ? Center(child: CircularProgressIndicator())
: PDFViewerScaffold( //....
For a really nice and clean code you should move pdf generation stuff into a separate class (or at least file) and you should also take into consideration how much time is acceptable for such a task (timeout).
PS The reason why your code works with "pointless UI" is because your fingers are slower than file generation, by the time you've tapped on RaisedButton the file has been already created and luckily has a path. That first code also doesn't do any checks, so if file creation fails for whatever reason you'd most probably end up with a crash or a blank screen.
EDIT: Bellow is a full example of how I would go about doing this. It should make your app a lot more resilient to crashes.
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io';
import 'dart:convert';
import 'package:flutter_full_pdf_viewer/full_pdf_viewer_scaffold.dart';
import 'package:path_provider/path_provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: PdfBase64Viewer(),
);
}
}
Future<File> createFile(String base64String) async {
final fileName = "test";
final fileType = "pdf";
var decodedBytes = base64Decode(base64String);
String dir = (await getApplicationDocumentsDirectory()).path;
// uncomment the line bellow to trigger an Exception
// dir = null;
File file = new File('$dir/$fileName.$fileType');
// uncomment the line bellow to trigger an Error
// decodedBytes = null;
await file.writeAsBytes(decodedBytes.buffer.asUint8List());
// uncomment the line bellow to trigger the TimeoutException
// await Future.delayed(const Duration(seconds: 6),() async {});
return file;
}
class PdfBase64Viewer extends StatefulWidget {
const PdfBase64Viewer();
#override
_PdfBase64ViewerState createState() => new _PdfBase64ViewerState();
}
class _PdfBase64ViewerState extends State<PdfBase64Viewer> {
bool isFileGood = true;
String pathPDF = "";
/*
Bellow is a b64 pdf, the smallest one I could find that has some content and it fits within char limit of StackOverflow answer.
It might not be 100% valid, ergo some readers might not accept it, so I recommend swapping it out for a proper PDF.
Output of the PDF should be a large "Test" text aligned to the center left of the page.
source: https://stackoverflow.com/questions/17279712/what-is-the-smallest-possible-valid-pdf/17280876
*/
String b64String = """JVBERi0xLjIgCjkgMCBvYmoKPDwKPj4Kc3RyZWFtCkJULyA5IFRmKFRlc3QpJyBFVAplbmRzdHJlYW0KZW5kb2JqCjQgMCBvYmoKPDwKL1R5cGUgL1BhZ2UKL1BhcmVudCA1IDAgUgovQ29udGVudHMgOSAwIFIKPj4KZW5kb2JqCjUgMCBvYmoKPDwKL0tpZHMgWzQgMCBSIF0KL0NvdW50IDEKL1R5cGUgL1BhZ2VzCi9NZWRpYUJveCBbIDAgMCA5OSA5IF0KPj4KZW5kb2JqCjMgMCBvYmoKPDwKL1BhZ2VzIDUgMCBSCi9UeXBlIC9DYXRhbG9nCj4+CmVuZG9iagp0cmFpbGVyCjw8Ci9Sb290IDMgMCBSCj4+CiUlRU9G""";
#override
void initState() {
super.initState();
Future.delayed(Duration.zero,() async {
File tmpFile;
try {
tmpFile = await createFile(b64String).timeout(
const Duration(seconds: 5));
} on TimeoutException catch (ex) {
// executed when an error is a type of TimeoutException
print('PDF taking too long to generate! Exception: $ex');
isFileGood = false;
} on Exception catch (ex) {
// executed when an error is a type of Exception
print('Some other exception was caught! Exception: $ex');
isFileGood = false;
} catch (err) {
// executed for errors of all types other than Exception
print("An error was caught! Error: $err");
isFileGood = false;
}
setState(() {
if(tmpFile != null){
pathPDF = tmpFile.path;
}
});
});
}
#override
Widget build(BuildContext context) {
return (!isFileGood) ? Scaffold(body: Center(child: Text("Something went wrong, can't display PDF!")))
: (pathPDF == null || pathPDF.isEmpty) ? Scaffold(body: Center(child: CircularProgressIndicator()))
: PDFViewerScaffold(
appBar: AppBar(
title: Text("Document"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.share),
onPressed: () {},
),
],
),
path: pathPDF);
}
}
I want to share an image that I took from the CameraController.
I location of the file is as example /data/user/0/com.user.test/cache/2019-09-10 16:32:52.281842.png
How it is possible to share this local image?
I added these two line for read/write to local storage:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
I use the share component from https://pub.dev/packages/esys_flutter_share which works great.
void _sharePicture() async {
print('Share picture');
print(this.imagePath);
final ByteData bytes = await rootBundle.load(this.imagePath);
await Share.file('esys image', 'esys.png', bytes.buffer.asUint8List(), 'image/png', text: 'My optional text.');
}
this.imagePath is the local location of the file: :/data/user/0/com.user.test/cache/2019-09-10 16:32:52.281842.png
Do you first have to save the image? And the use it for sharing? How is it possible to share this local image?
The idea is share Uint8List
This demo use camera_camera package's example. https://github.com/gabuldev/camera_camera/tree/master/example
camera_camera package https://pub.dev/packages/camera_camera is an greate package have well made features and use camera plugin inside
code snippet
after click take picture, the system return a file (val in this example), read bytes and transfer to Uint8List
print("path ${val}");
List<int> bytes = await val.readAsBytes();
Uint8List ubytes = Uint8List.fromList(bytes);
await Share.file('ESYS AMLOG', 'amlog.jpg', ubytes, 'image/jpg');
full code
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:camera_camera/camera_camera.dart';
import 'dart:typed_data';
import 'package:esys_flutter_share/esys_flutter_share.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: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
File val;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Rully")),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.camera_alt),
onPressed: () async {
val = await showDialog(
context: context,
builder: (context) => Camera(
mode: CameraMode.fullscreen,
orientationEnablePhoto: CameraOrientation.landscape,
/*
imageMask: CameraFocus.square(
color: Colors.black.withOpacity(0.5),
),
*/
));
print("path ${val}");
List<int> bytes = await val.readAsBytes();
Uint8List ubytes = Uint8List.fromList(bytes);
await Share.file('ESYS AMLOG', 'amlog.jpg', ubytes, 'image/jpg');
setState(() {});
}),
body: Center(
child: Container(
height: MediaQuery.of(context).size.height * 0.7,
width: MediaQuery.of(context).size.width * 0.8,
child: val != null
? Image.file(
val,
fit: BoxFit.contain,
)
: Text("Tire a foto"))));
}
}
demo screen
In camera_camera example take picture button will show in landscape mdoe
file path display in bottom
For camera plugin official example, I only change the following
code snippet
void onTakePictureButtonPressed() {
takePicture().then((String filePath) async{
if (mounted) {
setState(() {
imagePath = filePath;
videoController?.dispose();
videoController = null;
});
if (filePath != null) {
showInSnackBar('Picture saved to $filePath');
File val = File(filePath);
List<int> bytes = await val.readAsBytes();
Uint8List ubytes = Uint8List.fromList(bytes);
await Share.file('ESYS AMLOG', 'amlog.jpg', ubytes, 'image/jpg');
}
}
});
}