如何在 Flutter 中使用 Dio 和 multi_image_picker 插件上传多张图片

Posted

技术标签:

【中文标题】如何在 Flutter 中使用 Dio 和 multi_image_picker 插件上传多张图片【英文标题】:How to upload multiple images using Dio and multi_image_picker plugin in Flutter 【发布时间】:2020-05-03 22:19:01 【问题描述】:

我想在 Flutter 中使用 Dio 和 multi_image_picker 插件上传多张图片。

List<Asset> 这是问题所在,因为我无法从 List<Asset> 转换为 List<File>,所以如果您有任何解决方案,请帮助我。

尝试使用:

    multi_image_picker: ^4.6.1

    dio: ^3.0.4

谢谢

博纳 SR。

import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:merchantside/helper/colorhelper.dart';
import 'package:merchantside/merchantside/login.dart';
import 'dart:async';
import 'package:multi_image_picker/multi_image_picker.dart';

class ListImages extends StatefulWidget 
  String errorMessage = "";
  @override
  _ListImagesState createState() => new _ListImagesState();


class _ListImagesState extends State<ListImages> 
  List<Asset> images = List<Asset>();
  List<File> listImages = [];
  @override
  void initState() 
    super.initState();
  

  Widget buildGridView() 
    return GridView.count(
      crossAxisCount: 3,
      children: List.generate(images.length, (index) 
        Asset asset = images[index];
        return AssetThumb(
          asset: asset,
          width: 300,
          height: 300,
        );
      ),
    );
  


  void _uploadFiles() async 
    String uid = await FlutterSecureStorage().read(key: "getTocken");
    try 
      var dio = Dio();
      FormData formData = new FormData.fromMap(
        "pictures[]": images, 
      );
      Response resp = await dio.post(
        mainUrl + 'merchant/upload-galleries',
        data: formData,
        onSendProgress: (int sent, int total) 
          //
        , 
        options: Options(
          headers: 
            HttpHeaders.authorizationHeader: uid,
          ,
        )
      );
      if(resp.statusCode == 200) 
        print("============= Print Resp data: ");
        print(resp.data);
      

     catch (e) 
      print(e);
    
  

  Future<void> loadAssets() async 
    List<Asset> resultList = List<Asset>();
    try 
      resultList = await MultiImagePicker.pickImages(
        maxImages: 6,
        enableCamera: true,
        selectedAssets: images,
        cupertinoOptions: CupertinoOptions(takePhotoIcon: "chat"),
        materialOptions: MaterialOptions(
          actionBarColor: "#abcdef",
          actionBarTitle: "Example App",
          allViewTitle: "All Photos",
          useDetailsView: false,
          selectCircleStrokeColor: "#000000",
        ),
      );
     on Exception catch (e) 
      print(e);
    

    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) return;
    setState(() 
      images = resultList;
    );
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        heroTag: "btn1",
        backgroundColor: ColorHelper.orange,
        child: Icon(Icons.add_photo_alternate),
        onPressed: loadAssets,
      ),
      appBar: new AppBar(
        title: Text('បញ្ជីរូបភាព'),
        backgroundColor: ColorHelper.orange,
      ),
      body: Column(
        children: <Widget>[
          //Error message
          errorMessage != "" ? 
          Container(
            margin: EdgeInsets.only(left: 10, right: 10, top: 10),
            height: 50,
            decoration: BoxDecoration(
              borderRadius: BorderRadius.all(Radius.circular(4)),
              color: ColorHelper.red.withOpacity(0.5),
            ),
            child: Center(
              child: Text("$errorMessage", style: TextStyle(color: ColorHelper.swhite, fontSize: 15),),
            ),
          ):
          Container(),

          Expanded(
            child: Container(
              margin: EdgeInsets.only(left: 10, right: 10, top: 10),
              child: buildGridView(),
            ),
          ),
          SafeArea(
            child: Container(
              margin: EdgeInsets.all(10),
              decoration: BoxDecoration(
                color: ColorHelper.green,
                borderRadius: BorderRadius.all(Radius.circular(4))
              ),
              height: 50,
              child: InkWell(
                onTap: () 
                  if(images.length > 0) 
                    setState(() 
                      errorMessage = "";
                    );
                    // Call function upload multiple files
                    _uploadFiles();
                   else 
                    setState(() 
                      errorMessage = "សូមបញ្ជូលរូបភាព";
                    );
                   
                ,
                child: Center(
                  child: Text("រួចរាល់", style: TextStyle(color: ColorHelper.swhite, fontSize: 15, fontWeight: FontWeight.w500,),),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  

【问题讨论】:

欢迎来到 ***!这看起来是一个合理的问题 - 但如果您发布您尝试过的代码,它可能会有所帮助。 你好亲爱的,现在我准备更新我的源代码......请帮助我!谢谢! 您可以使用file_picker 并直接获取选择的文件列表。它支持多选并提供文件路径作为响应。这将是最简单的方法。 【参考方案1】:
   FormData formData = FormData.fromMap(
      'id': 'ZE8980',
      'name': 'myTitle',
      'age':'22',
      'order_images': [
        for (var file in images)

         // ....SPRED images

        ...
          await MultipartFile.fromFile(getImage(file).imageFile.path,
              filename: getImage(file).imageFile.path.split('/').last)
        .toList()
      ]
    );
    Dio dio = new Dio();
    var response = await dio.post(url, data: formData);

【讨论】:

【参考方案2】:

简单有效:

Map<String, dynamic> params = Map();
params['images'] = null;
Map<String, dynamic> headers = 
  HttpHeaders.contentTypeHeader: 'multipart/form-data',
;
List<MultipartFile> multipart = List<MultipartFile>();

//listImage is your list assets.
for (int i = 0; i < listImage.length; i++) 
  ByteData byteData = await listImage[i].getByteData();
  List<int> imageData = byteData.buffer.asUint8List();
  print("name: $listImage[i].name");
  String fileName = "$listImage[i].name";
  multipart.add(MultipartFile.fromBytes(imageData, filename: fileName, contentType: MediaType("image", "jpg"),));

if (multipart.isNotEmpty)
  params['images'] = multipart;


FormData formData = new FormData.fromMap(params);

【讨论】:

【参考方案3】:

您不需要将 Asset 转换为 File,您可以将 is 作为字节数组发送。

只需要创建每个图像的Multipart对象。

可以从 Assets 中获取字节数组

 ByteData byteData = await asset.getByteData();
  List<int> imageData = byteData.buffer.asUint8List();

那么就可以通过MultipartFile.fromBytes()方法了。

它看起来像,

  String url = "upload/url";

          List<Asset> images = List<Asset>();
          List<MultipartFile> multipartImageList = new List<MultipartFile>();
          if (null != images) 
            for (Asset asset in images) 
              ByteData byteData = await asset.getByteData();
              List<int> imageData = byteData.buffer.asUint8List();
              MultipartFile multipartFile = new MultipartFile.fromBytes(
                imageData,
                filename: 'load_image',
                contentType: MediaType("image", "jpg"),
              );
              multipartImageList.add(multipartFile);
            

            FormData formData = FormData.fromMap(
              "multipartFiles": multipartImageList,
              "userId": '1'
            );

            Dio dio = new Dio();
            var response = await dio.post(url, data: formData);
          

FYI

【讨论】:

【参考方案4】:

我在我的应用程序中使用了 dio 和 multi_image 插件并且它工作正常。我正在做的是在这里给我的代码 sn-ps。您可以说这是工作版本,应该可以在任何应用程序中使用。

集团类

final imageController = BehaviorSubject<List<Asset>>();
StreamSink<List<Asset>> get sinkImages =>
  imageController.sink;
Stream<List<Asset>> get getImages => imageController.stream;

Future<void> loadImages(imageCount) async 
List<Asset> imageList = List<Asset>();
String error = 'No Error Dectected';

try 
  imageList = await MultiImagePicker.pickImages(
    maxImages: imageCount,
    enableCamera: true,
    cupertinoOptions: CupertinoOptions(
      takePhotoIcon: "chat",
    ),
    materialOptions: MaterialOptions(
      actionBarColor: "#0A73B1",
      statusBarColor: "#0A73B1",
      actionBarTitle: "Select Image",
      allViewTitle: "All Photos",
      useDetailsView: false,
      selectCircleStrokeColor: "#000000",
    ),
  );
 on Exception catch (e) 
  error = e.toString();
  print(error);


_error = error;
imageController.add(imageList);

//发起api调用时调用该方法

Future<GenericResponse> uploadImageRequest() async 
      RequestModel reqModel = RequestModel(
                     uploadImageList: getImages);

      final SharedPref prefs = SharedPref();
      String token = await prefs.readString(accessToken);

      GenericResponse response = await _application.repository
          .uploadRequest(reqModel: reqModel, accessToken: token);

      return response;
    

所以你已经得到了你的图片列表。并将这些列表设置到您的请求模型类中。现在让我们使用 dio 将这些图像作为多部分发送。

API 类方法

//从存储库类调用

Future<GenericResponse> uploadRequest(
  RequestModel reqModel, String accessToken) async 
return await _apiProvider.uploadRequest(
    reqModel: reqModel, accessToken: accessToken);

//上传图片api请求

Future<ResponseModel> uploadRequest(
  RequestModel requestModel, String accessToken) async 

List<MultipartFile> imageList = [];

imageList = await getMultiPartImages(requestModel.imageList);

FormData formData = FormData.fromMap(RequestModel(
        uploadImageList: imageList,
    .toJson());

try 
  Response response = await getDio().post(
    'api/endpoint',
    options: Options(headers: 
      'Authorization': 'Bearer $accessToken',
    ),
    data: formData,
  );
  return ResponseModel.fromJson(response.data);
 catch (error, stacktrace) 
  debugPrint("Exception occured: $error stackTrace: $stacktrace");
  return ResponseModel.withError(handleError(error));
 

//podo模型类

class RequestModel 
 var uploadImageList;

 RequestModel(
  this.uploadImageList);

 RequestModel.fromJson(Map<String, dynamic> json) 
  uploadImageList = json['image_list'];
 

Map<String, dynamic> toJson() 
 final Map<String, dynamic> data = new Map<String, dynamic>();
 data['image_list'] = this.uploadImageList;
 return data;


@override
String toString() 
  return toJson().toString();
 

【讨论】:

【参考方案5】:

您可以使用file_picker 并直接获取选择的文件列表。它支持多选并提供文件路径作为响应。这将是最简单的方法。

List<File> files = await FilePicker.getMultiFilePath(
          type: FileType.IMAGE);

【讨论】:

【参考方案6】:

以下是如何从Assets 列表中获取字节数组列表:

List<Asset> images;
...

List<ByteData> byteDataList = await Future.wait(images.map((Asset image) => image.getByteData()));

List<Uint8List> byteArrayList = byteDataList.map((ByteData byteData) 
  ByteBuffer byteBuffer = byteData.buffer;
  return byteBuffer.asUint8List(byteData.offsetInBytes, byteBuffer.lengthInBytes);
).toList();

现在,您可以使用字节数组列表来使用您选择的网络客户端构建有效负载。对于那些不使用 3rd 方网络客户端的用户,这将是一个 MultipartRequest 和多个 MultipartFile.fromBytes

【讨论】:

【参考方案7】:

当你从图库中挑选图片时,调用getFileList(),然后调用set state,首先使用文件列表的全局变量,每次再次挑选图片时清除这个列表。

  List<File> listFile = List<File>();
  images = resultList;
  _error = error;
   getFileList();

 void getFileList() async
  listFile.clear();
 for(int i=0; i<images.length; i++)
  var path= await images[i].filePath;
  print(path);
  var file=await getImageFileFromAssets(path);
  print(file);
  listFile.add(file);

 setState(() 

 );

getImageFileFromAsset 用于将资产转换为文件

Future<File> getImageFileFromAsset(String path) async 
final file = File(path);
return file;

并在表单数据中使用 listFile。

【讨论】:

你好 Avinash,当我尝试用你的解决方案解决这个问题时,出现了这个问题。 var path=等待图像[i].filePath;未定义的文件路径。 images 是一个由选取器返回的资产列表 它是asset类的一个方法 /// 请求原图文件路径 Future get filePath return MultiImagePicker.requestFilePath(_identifier); 检查您的资产类别 您拥有 Asset 列表,因此您可以轻松地从 Asset 对象中获取路径

以上是关于如何在 Flutter 中使用 Dio 和 multi_image_picker 插件上传多张图片的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 dio 从本地主机:3000 切换到准备在 Flutter 中生产的东西?

如何使用 Dio 或 http 在 Flutter 中通过 GET 请求发送参数

flutter中的dio包如何在这段代码中加入base-url和url和apikey

flutter中dio网络get请求使用总结

Django:如何从颤动中读取使用 Dio 发送的图像

使用 Dio/bloc Flutter 处理错误