在 Flutter 中显示 PDF
Posted
技术标签:
【中文标题】在 Flutter 中显示 PDF【英文标题】:Displaying a PDF in Flutter 【发布时间】:2021-03-28 10:42:11 【问题描述】:我是 Flutter 的新手,正在努力从 Base64 字符串显示 PDF。我可以让它工作,但只有在列表和 PDF 之间有一个毫无意义的 UI。
应用程序只是崩溃并显示“与设备的连接丢失”,并且 android Studio 调试控制台中没有其他信息。
所以,这行得通:
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);
但这不是:
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);
谁能帮我理解为什么?
【问题讨论】:
【参考方案1】:您应该更加注意输出是(或不是)什么,这将使您的调试过程更快,并且大部分时间都不需要。
该应用程序只是崩溃并显示“与设备的连接丢失”而没有其他 信息出现在 Android Studio 调试控制台中。
这意味着你没有看到print("Local path: " + pathPDF);
这意味着你的应用还没有走到那一步。
至于它为什么会崩溃,没有任何测试,我可以看到你没有正确处理你的asyncs and futures。您只是假设它们会没事,并且假设它们会很快完成(在构建小部件之前)。您的 PDFViewerScaffold
很可能得到一个空字符串。一个简单的检查就可以解决这个问题:
@override
Widget build(BuildContext context)
return (pathPDF == null || pathPDF.isEmpty) ? Center(child: CircularProgressIndicator())
: PDFViewerScaffold( //....
对于一个非常漂亮和干净的代码,您应该将生成 pdf 的内容移到一个单独的类(或至少是文件)中,并且您还应该考虑这样的任务可以接受多少时间 (timeout)。
PS 你的代码使用“无意义的 UI”的原因是因为你的手指比文件生成慢,当你点击 RaisedButton 时,文件已经创建并且幸运的是有一个路径。第一个代码也不做任何检查,所以如果文件创建由于任何原因失败,您很可能会以崩溃或空白屏幕告终。
编辑: Bellow 是一个完整的例子,说明了我将如何做这件事。它应该使您的应用在崩溃时更具弹性。
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 *** 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://***.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);
【讨论】:
我确实看到路径在看到与设备的连接丢失之前立即回显,这就是为什么我为此挠头的原因。这是一个快速 POC,可以查看更广泛的应用程序是否可行。我将删除未来,看看是否能解决它。 不,这不是未来 - pathPDF 设置正确。 这正在工作。我没有删除未来,我测试了 null,如果它为 null,则返回不同的视图。当然,它从来都不是空的,它是空的。使用您的支票修复它!谢谢。 不客气。我在答案中添加了一个编辑以包含一个完整的示例,它还进行了一些改进,可以使您的应用程序不太容易出现故障和崩溃。一切顺利。以上是关于在 Flutter 中显示 PDF的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Flutter 应用程序中显示原生 Android 视图?