Flutter Web:如何为Firebase上传的每张图片显示ProgressIndicator
实现Flutter Web单图/多图上传带进度指示器(Firebase + FilePicker)
1. 依赖配置
先在pubspec.yaml中添加所需依赖:
dependencies: flutter: sdk: flutter firebase_core: ^2.15.0 firebase_storage: ^11.2.6 file_picker: ^5.5.0
执行flutter pub get后,在Firebase控制台配置Web项目,将初始化代码添加到web/index.html中。
2. 定义上传任务模型
创建类管理单个文件的上传状态:
class UploadItem { final XFile file; String fileName; double progress; bool isCompleted; String? error; UploadItem({ required this.file, required this.fileName, this.progress = 0.0, this.isCompleted = false, this.error, }); }
3. 主页面完整实现
用StatefulWidget管理上传列表与状态:
import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:file_picker/file_picker.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: FirebaseOptions( apiKey: "YOUR_API_KEY", authDomain: "YOUR_AUTH_DOMAIN", projectId: "YOUR_PROJECT_ID", storageBucket: "YOUR_STORAGE_BUCKET", messagingSenderId: "YOUR_SENDER_ID", appId: "YOUR_APP_ID", ), ); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: '图片上传进度示例', theme: ThemeData(primarySwatch: Colors.blue), home: const UploadPage(), ); } } class UploadPage extends StatefulWidget { const UploadPage({super.key}); @override State<UploadPage> createState() => _UploadPageState(); } class _UploadPageState extends State<UploadPage> { List<UploadItem> _uploadItems = []; bool _isPicking = false; // 选择图片并初始化上传项 Future<void> _pickImages() async { if (_isPicking) return; setState(() => _isPicking = true); final result = await FilePicker.platform.pickFiles( allowMultiple: true, type: FileType.image, withData: true, ); if (result != null) { final newItems = result.files.map((file) { return UploadItem( file: file, fileName: file.name, ); }).toList(); setState(() => _uploadItems.addAll(newItems)); _startUploads(newItems); } setState(() => _isPicking = false); } // 批量启动上传 void _startUploads(List<UploadItem> items) { for (var item in items) { _uploadFile(item); } } // 单个文件上传逻辑 Future<void> _uploadFile(UploadItem item) async { try { final storageRef = FirebaseStorage.instance .ref() .child('uploads/${DateTime.now().millisecondsSinceEpoch}_${item.fileName}'); final uploadTask = storageRef.putData( item.file.bytes!, SettableMetadata(contentType: item.file.extension != null ? 'image/${item.file.extension}' : 'image/jpeg'), ); // 监听上传进度 uploadTask.snapshotEvents.listen((taskSnapshot) { switch (taskSnapshot.state) { case TaskState.running: final progress = taskSnapshot.bytesTransferred / taskSnapshot.totalBytes; setState(() { item.progress = progress; }); break; case TaskState.success: setState(() { item.isCompleted = true; }); // 可在此获取下载URL:final downloadUrl = await storageRef.getDownloadURL(); break; case TaskState.paused: case TaskState.canceled: setState(() { item.error = '上传暂停或取消'; }); break; case TaskState.error: setState(() { item.error = '上传失败'; }); break; } }); } catch (e) { setState(() { item.error = '上传出错: ${e.toString()}'; }); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('图片上传进度')), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ ElevatedButton( onPressed: _pickImages, child: const Text('选择图片'), ), const SizedBox(height: 20), Expanded( child: _uploadItems.isEmpty ? const Center(child: Text('暂无上传任务')) : ListView.builder( itemCount: _uploadItems.length, itemBuilder: (context, index) { final item = _uploadItems[index]; return Card( margin: const EdgeInsets.only(bottom: 12), child: Padding( padding: const EdgeInsets.all(12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 图片预览 Container( width: 80, height: 80, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), image: DecorationImage( image: MemoryImage(item.file.bytes!), fit: BoxFit.cover, ), ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( item.fileName, style: const TextStyle(fontWeight: FontWeight.w500), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 8), // 上传中显示进度条 if (!item.isCompleted && item.error == null) LinearProgressIndicator(value: item.progress), // 上传完成提示 if (item.isCompleted) const Text('上传完成', style: TextStyle(color: Colors.green)), // 错误提示 if (item.error != null) Text(item.error!, style: TextStyle(color: Colors.red)), ], ), ), ], ), ), ); }, ), ), ], ), ), ); } }
关键细节说明
- 本地预览:通过
FilePicker的withData: true直接获取文件字节数据,用MemoryImage快速展示预览图,避免额外IO操作。 - 实时进度更新:利用Firebase Storage的
snapshotEvents监听上传快照,实时计算进度并通过setState刷新UI。 - 状态区分:针对上传中、完成、错误三种状态分别展示进度条、完成提示、错误信息,交互逻辑清晰。
- 文件名去重:上传路径加入时间戳,避免同名文件覆盖。
注意事项
- Firebase Storage规则需配置允许上传权限,测试阶段可使用临时规则:
rules_version = '2'; service firebase.storage { match /b/{bucket}/o { match /{allPaths=**} { allow read, write: if true; } } }
上线前需修改为安全规则(如仅允许已认证用户上传)。
2. 注意Firebase Storage的单文件大小限制,可在控制台调整。
内容的提问来源于stack exchange,提问作者AkMax
相关产品推荐
相关产品推荐

