如何使用HTTP和JSON在Flutter和Laravel中上传多个图像?

41ik7eoe  于 2023-06-07  发布在  Flutter
关注(0)|答案(1)|浏览(166)

我有问题,我想通过使用Laravel和Flutter上传多个图像,但我不知道如何
在Mysql数据库中,我有三个表:“用户和公寓”表和“照片中的照片”表具有三个字段:id,公寓_id,url
因此,在照片表中,我应该显示具有特定照片所有公寓,这意味着公寓表和照片表之间存在一对多关系
那么如何解决呢?
我试着写代码在Laravel在PhotoController
我从网上得到这个代码

{
        $request->validate([
            'apartment_id' => 'required|exists:apartments,id',
            'images' => 'required|array',
            'images.*' => 'image|mimes:jpeg,png,jpg|max:2048',
        ]);

        $apartmentId = $request->input('apartment_id');
        $images = $request->file('images');
        $uploadedImageIds = [];

        foreach ($images as $image) {
            $path = $image->store('your_directory');

            $photo = new Photo();
            $photo->apartment_id = $apartmentId;
            $photo->url = $path;
            $photo->save();

            $uploadedImageIds[] = $photo->id;
        }

        return response()->json(['message' => 'Images uploaded successfully', 'image_ids' => $uploadedImageIds]);
    }

我有一个代码在抖动
此代码来自互联网

import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
import 'package:http/http.dart' as http;
// import 'package:video_player/video_player.dart';
class AddImages extends StatefulWidget {
  const AddImages({super.key, this.title});
  final String? title;
  @override
  State<AddImages> createState() => _AddImagesState();
}
class _AddImagesState extends State<AddImages> {
  List<XFile>? _imageFileList;
  void _setImageFileListFromFile(XFile? value) {
    _imageFileList = value == null ? null : <XFile>[value];
  }
  dynamic _pickImageError;
  String? _retrieveDataError;
  final ImagePicker _picker = ImagePicker();
  final TextEditingController maxWidthController = TextEditingController();
  final TextEditingController maxHeightController = TextEditingController();
  final TextEditingController qualityController = TextEditingController();
  Future<void> _onImageButtonPressed(ImageSource source,
      {BuildContext? context, bool isMultiImage = false}) async {
    if (isMultiImage) {
      await _displayPickImageDialog(context!,
          (double? maxWidth, double? maxHeight, int? quality) async {
        try {
          final List<XFile> pickedFileList = await _picker.pickMultiImage(
            maxWidth: maxWidth,
            maxHeight: maxHeight,
            imageQuality: quality,
          );
          setState(() {
            _imageFileList = pickedFileList;
          });
        } catch (e) {
          setState(() {
            _pickImageError = e;
          });
        }
      });
    } else {
      await _displayPickImageDialog(context!,
          (double? maxWidth, double? maxHeight, int? quality) async {
        try {
          final XFile? pickedFile = await _picker.pickImage(
            source: source,
            maxWidth: maxWidth,
            maxHeight: maxHeight,
            imageQuality: quality,
          );
          setState(() {
            _setImageFileListFromFile(pickedFile);
          });
        } catch (e) {
          setState(() {
            _pickImageError = e;
          });
        }
      });
    }
  }

  @override
  void dispose() {
    // _disposeVideoController();
    maxWidthController.dispose();
    maxHeightController.dispose();
    qualityController.dispose();
    super.dispose();
  }

  Widget _previewImages() {
    final Text? retrieveError = _getRetrieveErrorWidget();
    if (retrieveError != null) {
      return retrieveError;
    }
    if (_imageFileList != null) {
      return Semantics(
        // label: 'image_picker_example_picked_images',
        child: GridView.builder(
          key: UniqueKey(),
          itemBuilder: (BuildContext context, int index) {
            // Why network for web?
            // See https://pub.dev/packages/image_picker#getting-ready-for-the-web-platform
            return Stack(children: [
              Image.file(
                File(
                  _imageFileList![index].path,
                ),
                height: 110,
                width: 110,
                fit: BoxFit.cover,
              ),
              Positioned(
                  left: 15.0,
                  top: 2,
                  child: GestureDetector(
                    child: const Icon(
                      Icons.cancel,
                      color: Colors.white70,
                    ),
                    onTap: (){
                      setState(() {
                        _imageFileList!.removeAt(index);

                      });
                    },
                  ))
            ]);
          },
          itemCount: _imageFileList!.length,
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3,
            mainAxisSpacing: 5,
            crossAxisSpacing: 5,
            // childAspectRatio: 0,
            // mainAxisExtent: 5
          ),
        ),
      );
    } else if (_pickImageError != null) {
      return Text(
        'Pick image error: $_pickImageError',
        textAlign: TextAlign.center,
      );
    } else {
      return const Text(
        'تُعرض الصور المختارة هنا',
        style: TextStyle(fontFamily: 'IBM'),
        textAlign: TextAlign.center,
      );
    }
  }

  Widget _handlePreview() {
    return _previewImages();
  }

  Widget? checkArray() {
    if (_imageFileList != null) {
      Get.to(FourthStep());
    } else {
      
    }
    return null;
  }

  Future<void> retrieveLostData() async {
    final LostDataResponse response = await _picker.retrieveLostData();
    if (response.isEmpty) {
      return;
    }
    if (response.file != null) {
      setState(() {
        if (response.files == null) {
          _setImageFileListFromFile(response.file);
        } else {
          _imageFileList = response.files;
        }
      });
    } else {
      _retrieveDataError = response.exception!.code;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey.shade200,
      appBar: AppBar(
        backgroundColor: Colors.white,
        actions: [
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: OutlinedButton(
              onPressed: () {
                Navigator.pop(context);
              },
             
              child: const Text(" رجوع "),
            ),
          ),
          const Expanded(child: Text("")),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: ElevatedButton(
              onPressed: checkArray,
             
              child: const Text("تم"),
            ),
          ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.fromLTRB(10, 10, 10, 0),
        child: Center(
          child: !kIsWeb && defaultTargetPlatform == TargetPlatform.android
              ? FutureBuilder<void>(
                  future: retrieveLostData(),
                  builder:
                      (BuildContext context, AsyncSnapshot<void> snapshot) {
                    switch (snapshot.connectionState) {
                      case ConnectionState.none:
                      case ConnectionState.waiting:
                        return const Text(
                          '',
                          style: TextStyle(
                            fontFamily: 'IBM',
                          ),
                          textAlign: TextAlign.center,
                        );
                      case ConnectionState.done:
                        return _handlePreview();
                      case ConnectionState.active:
                        if (snapshot.hasError) {
                          return Text(
                            ': خطأ ${snapshot.error}}',
                            textAlign: TextAlign.center,
                          );
                        } else {
                          return const Text(
                            '',
                            textAlign: TextAlign.center,
                          );
                        }
                    }
                  },
                )
              : _handlePreview(),
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          Padding(
            padding: const EdgeInsets.only(top: 16.0),
            child: FloatingActionButton(
              onPressed: () {
                _onImageButtonPressed(
                  ImageSource.gallery,
                  context: context,
                  isMultiImage: true,
                );
              },
              heroTag: 'image0',
              backgroundColor: Colors.orange,
              tooltip: 'أضف صور من المعرض',
              child: const Icon(Icons.photo_library),
            ),
          ),
          Padding(
            padding: const EdgeInsets.only(top: 16.0),
            child: FloatingActionButton(
              onPressed: () {
                _onImageButtonPressed(ImageSource.camera, context: context);
              },
              heroTag: 'image1',
              tooltip: 'إلتقط صورة',
              backgroundColor: Colors.orange,
              child: const Icon(Icons.camera_alt),
            ),
          ),
        ],
      ),
    );
  }

  Text? _getRetrieveErrorWidget() {
    if (_retrieveDataError != null) {
      final Text result = Text(_retrieveDataError!);
      _retrieveDataError = null;
      return result;
    }
    return null;
  }

  Future<void> _displayPickImageDialog(
      BuildContext context, OnPickImageCallback onPick) async {
    return showDialog(
        context: context,
        builder: (BuildContext context) {
          return AlertDialog(
            title: const Text(
              'إضافة صور للشقة',
              style: TextStyle(
                fontFamily: 'IBM',
              ),
            ),
            actions: <Widget>[
              ElevatedButton(
                  style: fullButton,
                  child: const Text('إضافة'),
                  onPressed: () {
                    final double? width = maxWidthController.text.isNotEmpty
                        ? double.parse(maxWidthController.text)
                        : null;
                    final double? height = maxHeightController.text.isNotEmpty
                        ? double.parse(maxHeightController.text)
                        : null;
                    final int? quality = qualityController.text.isNotEmpty
                        ? int.parse(qualityController.text)
                        : null;
                    onPick(width, height, quality);
                    Navigator.of(context).pop();
                  }),
              Padding(
                padding: const EdgeInsets.fromLTRB(110, 0, 0, 0),
                child: OutlinedButton(
                  child: const Text(
                    'رجوع',
                  ),
                  onPressed: () {
                    Navigator.of(context).pop();
                  },
                  
                ),
              ),
            ],
          );
        });
  }
//for testing
  //---------------------------------------------------
  Future<void> uploadImages(List<XFile> imageFiles) async {
    var request = http.MultipartRequest(
      'POST',
      Uri.parse('http://10.0.2.2:8000/api/photo/uploadImages'),
    );

    for (var imageFile in imageFiles) {
      var multipartFile = await http.MultipartFile.fromPath(
        'images[]',
        imageFile.path,
      );
      request.files.add(multipartFile);
    }

    var response = await request.send();

    if (response.statusCode == 200) {
      print('Images uploaded successfully');
    } else {
      print('Image upload failed with status ${response.statusCode}');
    }
  }
  //---------------------------------------------------

}

typedef OnPickImageCallback = void Function(
    double? maxWidth, double? maxHeight, int? quality);
atmip9wb

atmip9wb1#

我知道如果你想实现没有外部依赖(像dio这样的包)的唯一方法是http.MultipartRequest。

Future<void> uploadImages(List<XFile> imageFiles) async {
var request = http.MultipartRequest(
  'POST',
  Uri.parse('http://10.0.2.2:8000/api/photo/uploadImages'),
);

for (var imageFile in imageFiles) {
  var stream = http.ByteStream(
    imageFile.openRead().cast(),
  ); // Convert the file to byte stream.
  var length = await imageFile.length(); // get image size
  var multipartFile = http.MultipartFile(
    'images', // ? or images[]. To you really need to label the parameter with a array definition at the end ?
    stream, //File as a stream
    length, //File size
    filename: imageFile.path.split('/').last, //File name without path.
  );
  request.files.add(multipartFile);
}

var response = await request.send();
if (response.statusCode == 200) {
  print('Images uploaded successfully');
} else {
  print('Error uploading images: ${response.reasonPhrase}');
}

}
你也可以使用dio来实现同样的功能。
根据官方文件:

final formData = FormData.fromMap({
 'name': 'dio',
 'date': DateTime.now().toIso8601String(),
 'file': await MultipartFile.fromFile('./text.txt', filename: 'upload.txt'),
 'files': [
   await MultipartFile.fromFile('./text1.txt', filename: 'text1.txt'),
   await MultipartFile.fromFile('./text2.txt', filename: 'text2.txt'),
    ]
  });
final response = await dio.post('/info', data: formData);

相关问题