Flutter异步图片上传API中添加加载Spinner与结果弹窗需求
解决Flutter图片上传时加载Spinner及结果弹窗问题
问题修复点
- 修正异步状态管理:
_isInAsyncCall需在请求开始前通过setState设为true,请求结束(无论成功失败)设为false - 修复请求响应处理:移除错误的
.then((value) => null),正确获取请求响应流 - 添加异常捕获:处理网络请求、JSON解析等可能的异常
- 实现加载Spinner遮罩:用
Stack包裹页面内容,根据异步状态显示半透明遮罩和加载指示器 - 添加结果弹窗:请求完成后用
showDialog展示API返回的消息
修改后的完整代码
import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:http/http.dart' as http; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'find waste collector'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { File? selectedImage; String? message; bool _isInAsyncCall = false; Future<void> uploadImage() async { if (selectedImage == null) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('请先选择图片')), ); } return; } try { // 开始请求,显示加载Spinner setState(() { _isInAsyncCall = true; }); final request = http.MultipartRequest( "POST", Uri.parse('https://f77f-2402-d000-a500-73e5-8c48-4e9a-dad0-c14f.in.ngrok.io/upload'), ); // MultipartRequest会自动设置Content-Type为multipart/form-data,无需手动添加 request.files.add(http.MultipartFile( "image", selectedImage!.readAsBytes().asStream(), selectedImage!.lengthSync(), filename: selectedImage!.path.split("/").last, )); request.fields['userId'] = 'value'; // 正确获取响应流 final streamedResponse = await request.send(); final res = await http.Response.fromStream(streamedResponse); final resJson = jsonDecode(res.body); message = resJson['message']; // 请求成功,显示结果弹窗 if (mounted) { showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('上传结果'), content: Text(message ?? '未获取到响应消息'), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('确定'), ), ], ), ); } } catch (e) { // 捕获异常,提示错误 if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('上传失败:${e.toString()}')), ); } } finally { // 请求结束,隐藏加载Spinner if (mounted) { setState(() { _isInAsyncCall = false; }); } } } Future<void> getImage() async { final pickedImage = await ImagePicker().pickImage(source: ImageSource.gallery); if (pickedImage != null) { setState(() { selectedImage = File(pickedImage.path); }); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Stack( children: [ Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ selectedImage == null ? const Text("please pick a image to upload") : Image.file(selectedImage!), const SizedBox(height: 20), TextButton.icon( style: ButtonStyle( backgroundColor: MaterialStateProperty.all( const Color.fromARGB(255, 62, 187, 0), ), ), onPressed: uploadImage, icon: const Icon(Icons.upload_file_rounded, color: Colors.black38), label: const Text( "Upload waste sample Image", style: TextStyle(color: Colors.black), ), ), ], ), ), // 加载遮罩和Spinner if (_isInAsyncCall) Container( color: Colors.black.withOpacity(0.5), child: const Center( child: CircularProgressIndicator(color: Colors.white), ), ), ], ), floatingActionButton: FloatingActionButton( onPressed: getImage, child: const Icon(Icons.add_a_photo), ), ); } }
关键修改说明
- 异步状态控制:在
uploadImage开头通过setState切换加载状态,finally块确保请求结束后无论结果如何都隐藏加载状态 - 响应处理修复:移除错误的响应截断代码,正确保留请求返回的流数据用于后续解析
- 冗余Header移除:
MultipartRequest会自动生成符合规范的请求头,手动添加会导致冲突 - 加载遮罩实现:通过
Stack层叠页面内容与遮罩,异步请求时显示半透明背景和加载指示器,阻止用户操作 - 结果展示与异常处理:成功时弹出对话框展示API返回消息,异常时通过底部提示告知用户错误信息
- 空安全处理:添加图片选择判断避免空指针,用
mounted确保组件存活状态下执行状态更新与弹窗操作
内容的提问来源于stack exchange,提问作者00gash
相关产品推荐
相关产品推荐

