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集成
WorkManager
与Firebase
库之间存在冲突问题(与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
更多关于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.cancel
和 uploader.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();
}
}