Flutter文件上传插件flutter_uploader的使用

Flutter文件上传插件flutter_uploader的使用

Flutter Uploader

这是一个用于创建和管理上传任务的插件。支持iOS和Android。

该插件基于Android的WorkManager和iOS的NSURLSessionUploadTask在后台模式下运行上传任务。

该插件受flutter_downloader插件启发。感谢Hung Duy Ha和Flutter社区提供了这些出色的插件和灵感。

iOS集成

注意事项:创建Flutter项目时,请使用-i swift选项(用Swift编写)

启用后台模式

可选配置:

配置每个主机的最大连接数

插件默认允许每个主机同时运行3个HTTP连接。你可以通过在Info.plist文件中添加以下代码来更改此数字:

<!-- 更改此数字以配置最大并发任务数 -->
<key>FUMaximumConnectionsPerHost</key>
<integer>3</integer>
配置最大并发上传操作数

插件默认允许同时运行3个上传操作。你可以通过在Info.plist文件中添加以下代码来更改此数字:

<!-- 更改此数字以配置最大并发任务数 -->
<key>FUMaximumUploadOperation</key>
<integer>3</integer>
配置请求超时

控制任务在放弃前等待额外数据的时间(以秒为单位)。你可以在Info.plist文件中添加以下代码来更改此数字:

<!-- 更改此数字以配置请求超时 -->
<key>FUTimeoutInSeconds</key>
<integer>3600</integer>
本地化通知消息

当应用程序未在前台运行时,插件会发送通知消息告知用户所有文件已上传。默认情况下,该消息为英文。你可以通过在Info.plist文件中添加并本地化以下消息来更改此内容(可以在此链接中找到有关Info.plist本地化的详细信息):

<key>FUAllFilesUploadedMessage</key>
<string>所有文件已上传</string>

Android集成

可选配置:

配置最大并发任务数

插件依赖于WorkManager库,而WorkManager根据可用处理器的数量来配置同时运行的任务数量。你可以通过在AndroidManifest.xml中添加以下代码来设置固定数量:

<provider
    android:name="androidx.work.impl.WorkManagerInitializer"
    android:authorities="${applicationId}.workmanager-init"
    android:enabled="false"
    android:exported="false" />

<provider
    android:name="com.bluechilli.flutteruploader.FlutterUploaderInitializer"
    android:authorities="${applicationId}.flutter-upload-init"
    android:exported="false">
    <!-- 更改此数字以配置最大并发任务数 -->
    <meta-data
        android:name="com.bluechilli.flutterupload.MAX_CONCURRENT_TASKS"
        android:value="3" />

    <!-- 更改此数字以配置上传HTTP请求的连接超时时间 -->
    <meta-data
        android:name="com.bluechilli.flutteruploader.UPLOAD_CONNECTION_TIMEOUT_IN_SECONDS"
        android:value="3600" />
</provider>
本地化通知消息

你可以通过本地化以下消息来本地化上传进度的通知消息(可以在此链接中找到有关Android字符串本地化的详细信息):

<string name="flutter_uploader_notification_started">开始上传</string>
<string name="flutter_uploader_notification_in_progress">正在上传</string>
<string name="flutter_uploader_notification_canceled">上传取消</string>
<string name="flutter_uploader_notification_failed">上传失败</string>
<string name="flutter_uploader_notification_complete">上传完成</string>
Firebase集成

WorkManagerFirebase库之间存在冲突问题(与Guava库相关)。预计在新的Guava版本和Gradle构建工具版本中解决此问题。目前,你可以通过在build.gradle(位于android文件夹中)中添加一些代码来绕过这个问题:

allprojects {
    ...

    configurations.all {
        exclude group: 'com.google.guava', module: 'failureaccess'

        resolutionStrategy {
            eachDependency { details ->
                if ('guava' == details.requested.name) {
                    details.useVersion '27.0-android'
                }
            }
        }
    }
}

使用方法

导入包

import 'package:flutter_uploader/flutter_uploader.dart';

初始化上传器

final uploader = FlutterUploader();

创建新的上传任务

final taskId = await uploader.enqueue(
  url: "你的上传链接", // 必须:上传的目标链接
  files: [FileItem(filename: filename, savedDir: savedDir, fieldname: "file")], // 必须:要上传的文件列表
  method: UploadMethod.POST, // HTTP方法(POST或PUT或PATCH)
  headers: {"apikey": "api_123456", "userkey": "userkey_123456"}, // 上传请求中包含的任何数据
  data: {"name": "john"}, // 上传请求中包含的任何数据
  showNotification: false, // 发送本地通知(仅限Android)以跟踪上传状态
  tag: "上传 1"); // 上传任务的唯一标签

监听上传进度

final subscription = uploader.progress.listen((progress) {
  // 处理进度的代码
});

监听上传结果

final subscription = uploader.result.listen((result) {
  // 处理结果的代码
}, onError: (ex, stacktrace) {
  // 处理错误的代码
});

注意:当任务被取消时,它将作为带有状态为cancelled的异常发送到onError处理程序。

取消一个上传任务

uploader.cancel(taskId: taskId);

取消所有上传任务

uploader.cancelAll();

完整示例代码

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_uploader/flutter_uploader.dart';
import 'package:image_picker/image_picker.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart';

const String title = "文件上传示例应用";
const String uploadURL =
    "https://us-central1-flutteruploader.cloudfunctions.net/upload";

const String uploadBinaryURL =
    "https://us-central1-flutteruploader.cloudfunctions.net/upload/binary";

void main() => runApp(App());

class App extends StatefulWidget {
  final Widget child;

  App({Key key, this.child}) : super(key: key);

  _AppState createState() => _AppState();
}

class _AppState extends State<App> {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: title,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: UploadScreen(),
    );
  }
}

class UploadItem {
  final String id;
  final String tag;
  final MediaType type;
  final int progress;
  final UploadTaskStatus status;

  UploadItem({
    this.id,
    this.tag,
    this.type,
    this.progress = 0,
    this.status = UploadTaskStatus.undefined,
  });

  UploadItem copyWith({UploadTaskStatus status, int progress}) => UploadItem(
      id: this.id,
      tag: this.tag,
      type: this.type,
      status: status ?? this.status,
      progress: progress ?? this.progress);

  bool isCompleted() =>
      this.status == UploadTaskStatus.canceled ||
      this.status == UploadTaskStatus.complete ||
      this.status == UploadTaskStatus.failed;
}

enum MediaType { Image, Video }

class UploadScreen extends StatefulWidget {
  UploadScreen({Key key}) : super(key: key);

  [@override](/user/override)
  _UploadScreenState createState() => _UploadScreenState();
}

class _UploadScreenState extends State<UploadScreen> {
  FlutterUploader uploader = FlutterUploader();
  StreamSubscription _progressSubscription;
  StreamSubscription _resultSubscription;
  Map<String, UploadItem> _tasks = {};

  [@override](/user/override)
  void initState() {
    super.initState();
    _progressSubscription = uploader.progress.listen((progress) {
      final task = _tasks[progress.tag];
      print("进度: ${progress.progress} , 标签: ${progress.tag}");
      if (task == null) return;
      if (task.isCompleted()) return;
      setState(() {
        _tasks[progress.tag] =
            task.copyWith(progress: progress.progress, status: progress.status);
      });
    });
    _resultSubscription = uploader.result.listen((result) {
      print(
          "ID: ${result.taskId}, 状态: ${result.status}, 响应: ${result.response}, 状态码: ${result.statusCode}, 标签: ${result.tag}, 头: ${result.headers}");

      final task = _tasks[result.tag];
      if (task == null) return;

      setState(() {
        _tasks[result.tag] = task.copyWith(status: result.status);
      });
    }, onError: (ex, stacktrace) {
      print("异常: $ex");
      print("堆栈跟踪: $stacktrace" ?? "无堆栈跟踪");
      final exp = ex as UploadException;
      final task = _tasks[exp.tag];
      if (task == null) return;

      setState(() {
        _tasks[exp.tag] = task.copyWith(status: exp.status);
      });
    });
  }

  [@override](/user/override)
  void dispose() {
    super.dispose();
    _progressSubscription?.cancel();
    _resultSubscription?.cancel();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('插件示例应用'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: <Widget>[
            Container(height: 20.0),
            Text(
              'multipart/form-data 上传',
              style: Theme.of(context).textTheme.subhead,
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                RaisedButton(
                  onPressed: () => getImage(binary: false),
                  child: Text("上传图片"),
                ),
                Container(width: 20.0),
                RaisedButton(
                  onPressed: () => getVideo(binary: false),
                  child: Text("上传视频"),
                )
              ],
            ),
            Container(height: 20.0),
            Text(
              '二进制上传',
              style: Theme.of(context).textTheme.subhead,
            ),
            Text('这将上传所选文件为二进制格式'),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                RaisedButton(
                  onPressed: () => getImage(binary: true),
                  child: Text("上传图片"),
                ),
                Container(width: 20.0),
                RaisedButton(
                  onPressed: () => getVideo(binary: true),
                  child: Text("上传视频"),
                )
              ],
            ),
            Expanded(
              child: ListView.separated(
                padding: EdgeInsets.all(20.0),
                itemCount: _tasks.length,
                itemBuilder: (context, index) {
                  final item = _tasks.values.elementAt(index);
                  print("${item.tag} - ${item.status}");
                  return UploadItemView(
                    item: item,
                    onCancel: cancelUpload,
                  );
                },
                separatorBuilder: (context, index) {
                  return Divider(
                    color: Colors.black,
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }

  String _uploadUrl({[@required](/user/required) bool binary}) {
    if (binary) {
      return uploadBinaryURL;
    } else {
      return uploadURL;
    }
  }

  Future getImage({[@required](/user/required) bool binary}) async {
    var image = await ImagePicker.pickImage(source: ImageSource.gallery);
    if (image != null) {
      final String filename = basename(image.path);
      final String savedDir = dirname(image.path);
      final tag = "图片上传 ${_tasks.length + 1}";
      var url = _uploadUrl(binary: binary);
      var fileItem = FileItem(
        filename: filename,
        savedDir: savedDir,
        fieldname: "file",
      );

      var taskId = binary
          ? await uploader.enqueueBinary(
              url: url,
              file: fileItem,
              method: UploadMethod.POST,
              tag: tag,
              showNotification: true,
            )
          : await uploader.enqueue(
              url: url,
              data: {"name": "john"},
              files: [fileItem],
              method: UploadMethod.POST,
              tag: tag,
              showNotification: true,
            );

      setState(() {
        _tasks.putIfAbsent(
            tag,
            () => UploadItem(
                  id: taskId,
                  tag: tag,
                  type: MediaType.Image,
                  status: UploadTaskStatus.enqueued,
                ));
      });
    }
  }

  Future getVideo({[@required](/user/required) bool binary}) async {
    var video = await ImagePicker.pickVideo(source: ImageSource.gallery);
    if (video != null) {
      final String savedDir = dirname(video.path);
      final String filename = basename(video.path);
      final tag = "视频上传 ${_tasks.length + 1}";
      final url = _uploadUrl(binary: binary);

      var fileItem = FileItem(
        filename: filename,
        savedDir: savedDir,
        fieldname: "file",
      );

      var taskId = binary
          ? await uploader.enqueueBinary(
              url: url,
              file: fileItem,
              method: UploadMethod.POST,
              tag: tag,
              showNotification: true,
            )
          : await uploader.enqueue(
              url: url,
              data: {"name": "john"},
              files: [fileItem],
              method: UploadMethod.POST,
              tag: tag,
              showNotification: true,
            );

      setState(() {
        _tasks.putIfAbsent(
            tag,
            () => UploadItem(
                  id: taskId,
                  tag: tag,
                  type: MediaType.Video,
                  status: UploadTaskStatus.enqueued,
                ));
      });
    }
  }

  Future cancelUpload(String id) async {
    await uploader.cancel(taskId: id);
  }
}

typedef CancelUploadCallback = Future<void> Function(String id);

class UploadItemView extends StatelessWidget {
  final UploadItem item;
  final CancelUploadCallback onCancel;

  UploadItemView({
    Key key,
    this.item,
    this.onCancel,
  }) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    final progress = item.progress.toDouble() / 100;
    final widget = item.status == UploadTaskStatus.running
        ? LinearProgressIndicator(value: progress)
        : Container();
    final buttonWidget = item.status == UploadTaskStatus.running
        ? Container(
            height: 50,
            width: 50,
            child: IconButton(
              icon: Icon(Icons.cancel),
              onPressed: () => onCancel(item.id),
            ),
          )
        : Container();
    return Row(
      children: <Widget>[
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: <Widget>[
              Text(item.tag),
              Container(
                height: 5.0,
              ),
              Text(item.status.description),
              Container(
                height: 5.0,
              ),
              widget
            ],
          ),
        ),
        buttonWidget
      ],
    );
  }
}

更多关于Flutter文件上传插件flutter_uploader的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter文件上传插件flutter_uploader的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


flutter_uploader 是一个用于在 Flutter 应用中实现文件上传的插件。它支持多文件上传、后台上传、进度监听、暂停和恢复等功能。以下是使用 flutter_uploader 的基本步骤:

1. 添加依赖

首先,在 pubspec.yaml 文件中添加 flutter_uploader 依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_uploader: ^3.0.0

然后运行 flutter pub get 来安装依赖。

2. 初始化 FlutterUploader

在你的 Dart 文件中导入 flutter_uploader 并初始化它:

import 'package:flutter_uploader/flutter_uploader.dart';

final uploader = FlutterUploader();

3. 配置上传任务

你可以通过 UploadTask 来配置上传任务。以下是一个简单的上传文件的示例:

final task = UploadTask(
  url: 'https://your-server.com/upload', // 上传的URL
  files: [
    FileItem(
      path: '/path/to/your/file.txt', // 本地文件路径
      field: 'file', // 表单字段名
    ),
  ],
  method: UploadMethod.POST, // 上传方法,支持 POST, PUT, PATCH
  headers: {
    'Authorization': 'Bearer your_token', // 请求头
  },
  data: {
    'additional_field': 'value', // 额外的表单数据
  },
);

4. 开始上传

使用 uploader.enqueue 方法来开始上传任务:

final taskId = await uploader.enqueue(task);
print('Upload task started with ID: $taskId');

5. 监听上传进度

你可以通过监听 uploader.progress 来获取上传进度:

uploader.progress.listen((progress) {
  print('Upload progress: ${progress.progress}%');
});

6. 处理上传结果

你可以通过监听 uploader.result 来处理上传结果:

uploader.result.listen((result) {
  if (result.status == UploadTaskStatus.complete) {
    print('Upload complete: ${result.response}');
  } else if (result.status == UploadTaskStatus.failed) {
    print('Upload failed: ${result.response}');
  }
});

7. 暂停和恢复上传

你可以通过 uploader.canceluploader.resume 方法来暂停和恢复上传任务:

// 暂停上传
await uploader.cancel(taskId);

// 恢复上传
await uploader.resume(taskId);

8. 后台上传

flutter_uploader 支持后台上传,即使应用被关闭,上传任务仍会继续。你可以通过监听 uploader.onBackgroundHandler 来处理后台上传的结果:

uploader.onBackgroundHandler = (UploadTaskResponse response) {
  print('Background upload result: ${response.status}');
};

9. 取消上传

你可以通过 uploader.cancel 方法来取消上传任务:

await uploader.cancel(taskId);

10. 清理上传任务

在应用退出时,你可以通过 uploader.clearUploads 方法来清理所有上传任务:

await uploader.clearUploads();

完整示例

以下是一个完整的文件上传示例:

import 'package:flutter/material.dart';
import 'package:flutter_uploader/flutter_uploader.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: UploadPage(),
    );
  }
}

class UploadPage extends StatefulWidget {
  [@override](/user/override)
  _UploadPageState createState() => _UploadPageState();
}

class _UploadPageState extends State<UploadPage> {
  final uploader = FlutterUploader();
  String? taskId;

  [@override](/user/override)
  void initState() {
    super.initState();
    uploader.progress.listen((progress) {
      print('Upload progress: ${progress.progress}%');
    });

    uploader.result.listen((result) {
      if (result.status == UploadTaskStatus.complete) {
        print('Upload complete: ${result.response}');
      } else if (result.status == UploadTaskStatus.failed) {
        print('Upload failed: ${result.response}');
      }
    });
  }

  Future<void> startUpload() async {
    final directory = await getApplicationDocumentsDirectory();
    final file = File('${directory.path}/test.txt');
    await file.writeAsString('Hello, world!');

    final task = UploadTask(
      url: 'https://your-server.com/upload',
      files: [FileItem(path: file.path, field: 'file')],
      method: UploadMethod.POST,
      headers: {'Authorization': 'Bearer your_token'},
      data: {'additional_field': 'value'},
    );

    taskId = await uploader.enqueue(task);
    print('Upload task started with ID: $taskId');
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('File Upload'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: startUpload,
          child: Text('Upload File'),
        ),
      ),
    );
  }

  [@override](/user/override)
  void dispose() {
    uploader.clearUploads();
    super.dispose();
  }
}
回到顶部