Flutter 如何从 api 下载 pdf 文件
Posted
技术标签:
【中文标题】Flutter 如何从 api 下载 pdf 文件【英文标题】:Flutter How can download pdf file from api 【发布时间】:2020-11-24 10:41:39 【问题描述】:我刚开始学习颤振。我用 Nodejs 编写了一个 api 并部署到 heroku。 我通过 Postman 和浏览器测试了 api,它适用于字符串、json 和 pdf 文件。
如何让 Flutter 从 api 下载 pdf 文件
我的nodejs api
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/getPdfFile/:Name', function(req, res, next)
var path = require('path');
console.log(__dirname);
var file = path.join(__dirname, '../pdfs/'+req.params.Name+'.pdf');
res.download(file, function (err)
if (err)
console.log("Error");
console.log(err);
else
console.log("Success");
);
);
router.get('/getName', function(req, res, next)
res.json("foo": "bar");
);
router.get('/getNamee', function(req, res, next)
res.send('some string');
);
我的颤振 main.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main()
runApp(new MaterialApp(
home: new HomePage(),
));
class HomePage extends StatefulWidget
@override
HomePageState createState() => new HomePageState();
class HomePageState extends State<HomePage>
List data;
//apidenemee4.herokuapp.com/getPdfFile/SpaceX
Future getData() async
var response = await http.get(
Uri.encodeFull("https://mynodeapi4.herokuapp.com//getPdfFile/SpaceX"),
headers: "Accept": "application/json");
print(response.body);
@override
Widget build(BuildContext context)
return new Scaffold(
body: new Center(
child: new RaisedButton(
child: new Text("Get data"),
onPressed: getData,
),
),
);
【问题讨论】:
我的答案中的示例对您有用吗? 【参考方案1】:您可以使用flutter_downloader
- 下载和打开文件,path_provider
- 访问设备路径,permission_handler
- 处理设备存储权限。
请在androidManifest.xml
中为Android设备添加以下权限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
在application
标签内添加provider
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.snj07.flutter_app_stw.flutter_downloader.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
在src/main
里面创建一个xml
名字的文件夹,在provider_paths.xml
里面添加如下内容
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path
name="share"
path="external_files"/>
</paths>
请自定义以下示例以下载 PDF 文件并在您的项目中打开它。它取自 flutter_downloader
示例。我还根据最新插件更新了一些权限处理代码。
import 'dart:async';
import 'dart:io';
import 'dart:isolate';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
const debug = true;
void main() async
WidgetsFlutterBinding.ensureInitialized();
await FlutterDownloader.initialize(debug: debug);
runApp(new MyApp());
class MyApp extends StatelessWidget
@override
Widget build(BuildContext context)
final platform = Theme.of(context).platform;
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(
title: 'Downloader',
platform: platform,
),
);
class MyHomePage extends StatefulWidget with WidgetsBindingObserver
final TargetPlatform platform;
MyHomePage(Key key, this.title, this.platform) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
class _MyHomePageState extends State<MyHomePage>
final _documents = [
'name': 'PDF',
'link':
'http://darwinlogic.com/uploads/education/ios_Programming_Guide.pdf'
,
];
List<_TaskInfo> _tasks;
List<_ItemHolder> _items;
bool _isLoading;
bool _permissionReady;
String _localPath;
ReceivePort _port = ReceivePort();
@override
void initState()
super.initState();
_bindBackgroundIsolate();
FlutterDownloader.registerCallback(downloadCallback);
_isLoading = true;
_permissionReady = false;
_prepare();
@override
void dispose()
_unbindBackgroundIsolate();
super.dispose();
void _bindBackgroundIsolate()
bool isSuccess = IsolateNameServer.registerPortWithName(
_port.sendPort, 'downloader_send_port');
if (!isSuccess)
_unbindBackgroundIsolate();
_bindBackgroundIsolate();
return;
_port.listen((dynamic data)
if (debug)
print('UI Isolate Callback: $data');
String id = data[0];
DownloadTaskStatus status = data[1];
int progress = data[2];
final task = _tasks?.firstWhere((task) => task.taskId == id);
if (task != null)
setState(()
task.status = status;
task.progress = progress;
);
);
void _unbindBackgroundIsolate()
IsolateNameServer.removePortNameMapping('downloader_send_port');
static void downloadCallback(
String id, DownloadTaskStatus status, int progress)
if (debug)
print(
'Background Isolate Callback: task ($id) is in status ($status) and process ($progress)');
final SendPort send =
IsolateNameServer.lookupPortByName('downloader_send_port');
send.send([id, status, progress]);
@override
Widget build(BuildContext context)
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: Builder(
builder: (context) => _isLoading
? new Center(
child: new CircularProgressIndicator(),
)
: _permissionReady
? new Container(
child: new ListView(
padding: const EdgeInsets.symmetric(vertical: 16.0),
children: _items
.map((item) => item.task == null
? new Container(
padding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 8.0),
child: Text(
item.name,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.blue,
fontSize: 18.0),
),
)
: new Container(
padding: const EdgeInsets.only(
left: 16.0, right: 8.0),
child: InkWell(
onTap: item.task.status ==
DownloadTaskStatus.complete
? ()
_openDownloadedFile(item.task)
.then((success)
if (!success)
Scaffold.of(context)
.showSnackBar(SnackBar(
content: Text(
'Cannot open this file')));
);
: null,
child: new Stack(
children: <Widget>[
new Container(
width: double.infinity,
height: 64.0,
child: new Row(
crossAxisAlignment:
CrossAxisAlignment.center,
children: <Widget>[
new Expanded(
child: new Text(
item.name,
maxLines: 1,
softWrap: true,
overflow:
TextOverflow.ellipsis,
),
),
new Padding(
padding:
const EdgeInsets.only(
left: 8.0),
child: _buildActionForTask(
item.task),
),
],
),
),
item.task.status ==
DownloadTaskStatus
.running ||
item.task.status ==
DownloadTaskStatus.paused
? new Positioned(
left: 0.0,
right: 0.0,
bottom: 0.0,
child:
new LinearProgressIndicator(
value: item.task.progress /
100,
),
)
: new Container()
]
.where((child) => child != null)
.toList(),
),
),
))
.toList(),
),
)
: new Container(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 24.0),
child: Text(
'Please grant accessing storage permission to continue -_-',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.blueGrey, fontSize: 18.0),
),
),
SizedBox(
height: 32.0,
),
FlatButton(
onPressed: ()
_checkPermission().then((hasGranted)
setState(()
_permissionReady = hasGranted;
);
);
,
child: Text(
'Retry',
style: TextStyle(
color: Colors.blue,
fontWeight: FontWeight.bold,
fontSize: 20.0),
))
],
),
),
)),
);
Widget _buildActionForTask(_TaskInfo task)
if (task.status == DownloadTaskStatus.undefined)
return new RawMaterialButton(
onPressed: ()
_requestDownload(task);
,
child: new Icon(Icons.file_download),
shape: new CircleBorder(),
constraints: new BoxConstraints(minHeight: 32.0, minWidth: 32.0),
);
else if (task.status == DownloadTaskStatus.running)
return new RawMaterialButton(
onPressed: ()
_pauseDownload(task);
,
child: new Icon(
Icons.pause,
color: Colors.red,
),
shape: new CircleBorder(),
constraints: new BoxConstraints(minHeight: 32.0, minWidth: 32.0),
);
else if (task.status == DownloadTaskStatus.paused)
return new RawMaterialButton(
onPressed: ()
_resumeDownload(task);
,
child: new Icon(
Icons.play_arrow,
color: Colors.green,
),
shape: new CircleBorder(),
constraints: new BoxConstraints(minHeight: 32.0, minWidth: 32.0),
);
else if (task.status == DownloadTaskStatus.complete)
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
new Text(
'Open',
style: new TextStyle(color: Colors.green),
),
RawMaterialButton(
onPressed: ()
_delete(task);
,
child: Icon(
Icons.delete_forever,
color: Colors.red,
),
shape: new CircleBorder(),
constraints: new BoxConstraints(minHeight: 32.0, minWidth: 32.0),
)
],
);
else if (task.status == DownloadTaskStatus.canceled)
return new Text('Canceled', style: new TextStyle(color: Colors.red));
else if (task.status == DownloadTaskStatus.failed)
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
new Text('Failed', style: new TextStyle(color: Colors.red)),
RawMaterialButton(
onPressed: ()
_retryDownload(task);
,
child: Icon(
Icons.refresh,
color: Colors.green,
),
shape: new CircleBorder(),
constraints: new BoxConstraints(minHeight: 32.0, minWidth: 32.0),
)
],
);
else
return null;
void _requestDownload(_TaskInfo task) async
task.taskId = await FlutterDownloader.enqueue(
url: task.link,
headers: "auth": "test_for_sql_encoding",
savedDir: _localPath,
showNotification: true,
openFileFromNotification: true);
void _cancelDownload(_TaskInfo task) async
await FlutterDownloader.cancel(taskId: task.taskId);
void _pauseDownload(_TaskInfo task) async
await FlutterDownloader.pause(taskId: task.taskId);
void _resumeDownload(_TaskInfo task) async
String newTaskId = await FlutterDownloader.resume(taskId: task.taskId);
task.taskId = newTaskId;
void _retryDownload(_TaskInfo task) async
String newTaskId = await FlutterDownloader.retry(taskId: task.taskId);
task.taskId = newTaskId;
Future<bool> _openDownloadedFile(_TaskInfo task)
return FlutterDownloader.open(taskId: task.taskId);
void _delete(_TaskInfo task) async
await FlutterDownloader.remove(
taskId: task.taskId, shouldDeleteContent: true);
await _prepare();
setState(() );
Future<bool> _checkPermission() async
if (widget.platform == TargetPlatform.android)
Map<Permission, PermissionStatus> statuses = await [
Permission.storage,
].request();
if (statuses[Permission.storage] == PermissionStatus.denied)
if (await Permission.contacts.request().isGranted)
// Either the permission was already granted before or the user just granted it.
return true;
else
return true;
else
return true;
return false;
Future<Null> _prepare() async
final tasks = await FlutterDownloader.loadTasks();
int count = 0;
_tasks = [];
_items = [];
_tasks.addAll(_documents.map((document) =>
_TaskInfo(name: document['name'], link: document['link'])));
_items.add(_ItemHolder(name: 'Documents'));
for (int i = count; i < _tasks.length; i++)
_items.add(_ItemHolder(name: _tasks[i].name, task: _tasks[i]));
count++;
tasks?.forEach((task)
for (_TaskInfo info in _tasks)
if (info.link == task.url)
info.taskId = task.taskId;
info.status = task.status;
info.progress = task.progress;
);
_permissionReady = await _checkPermission();
_localPath = (await _findLocalPath()) + Platform.pathSeparator + 'Download';
final savedDir = Directory(_localPath);
bool hasExisted = await savedDir.exists();
if (!hasExisted)
savedDir.create();
setState(()
_isLoading = false;
);
Future<String> _findLocalPath() async
final directory = widget.platform == TargetPlatform.android
? await getExternalStorageDirectory()
: await getApplicationDocumentsDirectory();
return directory.path;
class _TaskInfo
final String name;
final String link;
String taskId;
int progress = 0;
DownloadTaskStatus status = DownloadTaskStatus.undefined;
_TaskInfo(this.name, this.link);
class _ItemHolder
final String name;
final _TaskInfo task;
_ItemHolder(this.name, this.task);
【讨论】:
我正在从需要header
Auth-key
formdata
的 API 获取数据,并在处理数据后返回像 Content-type:application/pdf
这样的 pdf 文件。现在您知道如何以 pdf 格式预览该文件以上是关于Flutter 如何从 api 下载 pdf 文件的主要内容,如果未能解决你的问题,请参考以下文章
从url下载pdf,在flutter中保存到android中的手机本地存储