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中的手机本地存储

Nodejs:如何从 REST API 下载 pdf 文件?

在flutter中与api分开显示pdf和图像的列表

如何让用户从 Flutter 应用程序下载/保存文件?

从 API URL 下载 PDF

如何在flutter中通过webview下载/创建pdf