3行代码搞定Flutter图片上传:dio+Image Picker实战指南

3行代码搞定Flutter图片上传:dio+Image Picker实战指南

【免费下载链接】dio 【免费下载链接】dio 项目地址: https://gitcode.com/gh_mirrors/dio/dio

你是否还在为Flutter应用中的图片上传功能头疼?选择图片时格式错乱、上传进度无法追踪、大文件上传频繁失败——这些问题是否让你束手无策?本文将通过dio网络库与Flutter Image Picker的完美配合,带你实现从相册选择到服务端接收的完整图片上传方案,解决90%的开发痛点。

读完本文你能得到:

  • 3行代码集成图片选择功能
  • 实时上传进度显示实现方案
  • 异常处理与用户友好提示
  • 生产环境优化配置清单

技术选型:为什么是dio+Image Picker?

在Flutter生态中,dio作为功能全面的HTTP客户端,提供了文件上传、进度监听、拦截器等核心能力,其官方文档显示已被超过10万个项目采用。而Flutter Image Picker则是社区首选的媒体选择工具,支持相册访问与相机拍摄,两者配合形成完整的图片处理闭环。

核心优势对比

功能dio+Image Picker原生HTTP+自定义选择
代码量约50行约200行
进度监听内置支持需要手动实现
错误处理完整异常体系需要自行封装
跨平台支持iOS/Android/Web需单独适配

环境准备与依赖配置

首先需要在pubspec.yaml中添加依赖:

dependencies:
  dio: ^5.4.0
  image_picker: ^1.0.7

执行安装命令:

flutter pub get

权限配置:

  • Android:在android/app/src/main/AndroidManifest.xml添加
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  • iOS:在ios/Runner/Info.plist添加
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册以选择图片</string>

完整实现步骤

1. 初始化dio实例

创建http.dart文件配置基础网络参数:

import 'package:dio/dio.dart';

final dio = Dio(BaseOptions(
  baseUrl: "https://api.example.com",
  connectTimeout: Duration(seconds: 5),
  receiveTimeout: Duration(seconds: 3),
));

// 添加日志拦截器便于调试
dio.interceptors.add(LogInterceptor(responseBody: true));

2. 实现图片选择功能

创建图片选择工具类image_picker_util.dart

import 'package:image_picker/image_picker.dart';

class ImagePickerUtil {
  static Future<String?> pickImage() async {
    final picker = ImagePicker();
    final pickedFile = await picker.pickImage(
      source: ImageSource.gallery,
      maxWidth: 800, // 压缩图片宽度
      imageQuality: 80, // 图片质量
    );
    return pickedFile?.path;
  }
}

3. 图片上传核心实现

在业务页面中集成上传功能:

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'http.dart';

class UploadPage extends StatefulWidget {
  @override
  _UploadPageState createState() => _UploadPageState();
}

class _UploadPageState extends State<UploadPage> {
  String? _imagePath;
  double _progress = 0.0;
  bool _isUploading = false;

  Future<void> _selectImage() async {
    final path = await ImagePickerUtil.pickImage();
    if (path != null) {
      setState(() => _imagePath = path);
    }
  }

  Future<void> _uploadImage() async {
    if (_imagePath == null) return;
    
    setState(() {
      _isUploading = true;
      _progress = 0.0;
    });

    final formData = FormData.fromMap({
      'file': await MultipartFile.fromFile(
        _imagePath!,
        filename: "image.jpg",
        contentType: MediaType("image", "jpeg"),
      ),
      'description': "用户头像"
    });

    try {
      final response = await dio.post(
        "/upload",
        data: formData,
        onSendProgress: (sent, total) {
          setState(() {
            _progress = sent / total;
          });
        },
      );

      if (response.statusCode == 200) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text("上传成功!")),
        );
      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text("上传失败: ${e.toString()}")),
      );
    } finally {
      setState(() => _isUploading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("图片上传")),
      body: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          children: [
            ElevatedButton(
              onPressed: _selectImage,
              child: Text("选择图片"),
            ),
            if (_imagePath != null) ...[
              SizedBox(height: 16),
              Image.file(
                File(_imagePath!),
                height: 200,
                fit: BoxFit.cover,
              ),
              SizedBox(height: 16),
              ElevatedButton(
                onPressed: _isUploading ? null : _uploadImage,
                child: Text("上传图片"),
              ),
              if (_isUploading) ...[
                SizedBox(height: 16),
                LinearProgressIndicator(value: _progress),
                SizedBox(height: 8),
                Text("${(_progress * 100).toStringAsFixed(0)}%"),
              ]
            ]
          ],
        ),
      ),
    );
  }
}

高级功能实现

1. 取消上传功能

通过CancelToken实现上传任务取消:

final cancelToken = CancelToken();

// 在上传请求中添加
dio.post(
  "/upload",
  data: formData,
  cancelToken: cancelToken,
);

// 取消按钮调用
ElevatedButton(
  onPressed: () => cancelToken.cancel("用户取消上传"),
  child: Text("取消"),
)

2. 多图上传实现

修改上传方法支持多文件:

Future<void> _uploadMultipleImages(List<String> paths) async {
  final formData = FormData();
  
  for (var path in paths) {
    formData.files.add(MapEntry(
      'files',
      await MultipartFile.fromFile(path, filename: "image_${DateTime.now().millisecondsSinceEpoch}.jpg"),
    ));
  }
  
  // 后续上传逻辑类似单图上传
}

常见问题解决方案

1. Android图片压缩问题

通过flutter_image_compress优化压缩质量:

import 'package:flutter_image_compress/flutter_image_compress.dart';

Future<File> compressImage(String path) async {
  final result = await FlutterImageCompress.compressAndGetFile(
    path,
    path.replaceAll(".jpg", "_compressed.jpg"),
    quality: 70,
    minWidth: 1024,
  );
  return File(result!.path);
}

2. 网络异常处理

完善异常处理逻辑:

try {
  // 上传请求
} on DioException catch (e) {
  if (e.type == DioExceptionType.connectionTimeout) {
    showError("网络连接超时");
  } else if (e.type == DioExceptionType.receiveTimeout) {
    showError("服务器响应超时");
  } else if (e.type == DioExceptionType.cancel) {
    showError("上传已取消");
  } else {
    showError("上传失败: ${e.message}");
  }
} catch (e) {
  showError("未知错误: $e");
}

性能优化建议

  1. 图片预处理:上传前进行压缩,建议宽度限制在1024px以内
  2. 分块上传:大文件采用分块上传(参考dio分块示例
  3. 缓存策略:对已上传图片进行本地缓存,避免重复上传
  4. 后台上传:结合workmanager实现应用退出后继续上传

总结与最佳实践

通过dio与Image Picker的组合,我们仅用不到200行代码就实现了完整的图片上传功能,包括选择、压缩、进度显示、取消和错误处理等核心特性。在实际项目中,建议:

  1. 将网络请求与UI逻辑分离,采用MVVM架构
  2. 对所有用户输入进行验证,特别是文件类型和大小
  3. 添加日志收集,便于线上问题排查
  4. 针对不同网络环境(WiFi/移动网络)调整上传策略

掌握这套方案后,你可以轻松应对各类图片上传场景,为你的Flutter应用提供流畅可靠的媒体上传体验。

点赞收藏本文,下期我们将带来"断点续传与秒传功能实现",敬请关注!

【免费下载链接】dio 【免费下载链接】dio 项目地址: https://gitcode.com/gh_mirrors/dio/dio

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值