sendFormData method

Future<Response> sendFormData(
  1. String endpoint, {
  2. Map<String, List<File?>>? files,
  3. List<File?>? singleFiles,
  4. String singleFieldName = 'image',
  5. Map<String, dynamic>? body,
  6. FormDataMethod method = FormDataMethod.post,
})

Supports:

  • Single-field multi-file uploads
  • Multi-field multi-file uploads
  • Multi-field single-file uploads
  • Mixed upload modes
  • Any combination of files + normal fields
  • Automatic MIME type detection for every file

PARAMETERS:

  • singleFiles : List<File?> for a single field
  • singleFieldName : Field name for singleFiles
  • files : Map<String, List<File?>> representing multi-field uploads
  • body : additional text fields (Map<String, dynamic>)
  • method : POST or PUT

The function automatically:

  • Filters out null files
  • Detects correct MIME types using package:mime
  • Converts everything into MultipartFile
  • Merges files + body into a single FormData object
  • Sends with Dio POST/PUT

USAGE EXAMPLES

EXAMPLE 1 — Single-field multi-file

await sendFormData( "/upload", singleFiles: file1, file2, singleFieldName: "images", body: {"title": "My Gallery"}, );

EXAMPLE 2 — Multi-field single-file

await sendFormData( "/upload", files: { "image_1": file1, "image_2": file2, }, body: {"user_id": 123}, );

EXAMPLE 3 — Multi-field multi-file

await sendFormData( "/upload", files: { "documents": doc1, doc2, "photos": p1, p2, p3, }, );

EXAMPLE 4 — Mixed usage

await sendFormData( "/upload-all", singleFiles: avatar, singleFieldName: "avatar", files: { "gallery": g1, g2, g3, "attachments": a1, a2, }, body: {"description": "Full upload"}, );

EXAMPLE 5 — Body only (no files)

await sendFormData( "/save", body: {"name": "John", "email": "john@mail.com"}, );

EXAMPLE 6 — PUT request

await sendFormData( "/update", method: FormDataMethod.put, singleFiles: profileImage, singleFieldName: "profile_image", );


Returns: Dio Response

Implementation

Future<Response> sendFormData(
  String endpoint, {
  Map<String, List<File?>>? files, // multi-field multi-file
  List<File?>? singleFiles, // single-field multi-file
  String singleFieldName = 'image', // default name for singleFiles
  Map<String, dynamic>? body, // normal fields
  FormDataMethod method = FormDataMethod.post,
}) async {
  final Map<String, dynamic> fileMap = {};

  /// INTERNAL HELPER
  /// Converts File -> MultipartFile with automatic MIME detection
  Future<MultipartFile> toMultipart(File file) async {
    final fileName = file.path.split('/').last;

    // Detect MIME type (fallback to binary stream)
    final mime = lookupMimeType(file.path) ?? 'application/octet-stream';

    return MultipartFile.fromFile(
      file.path,
      filename: fileName,
      contentType: MediaType.parse(mime),
    );
  }

  // Handle single-field multi-file
  if (singleFiles != null && singleFiles.isNotEmpty) {
    final validFiles = singleFiles.where((f) => f != null).toList();

    if (validFiles.isNotEmpty) {
      final multipartFiles = await Future.wait(
        validFiles.map((file) => toMultipart(file!)),
      );

      fileMap[singleFieldName] = multipartFiles;
    }
  }

  // Handle multi-field multi-file
  if (files != null && files.isNotEmpty) {
    for (final entry in files.entries) {
      final key = entry.key;
      final validFiles = entry.value.where((f) => f != null).toList();
      if (validFiles.isEmpty) continue;

      final multipartFiles = await Future.wait(
        validFiles.map((file) => toMultipart(file!)),
      );

      fileMap[key] = multipartFiles;
    }
  }

  // Merge body + files into FormData
  final formData = FormData.fromMap({
    ...?body,
    ...fileMap,
  });

  // Execute HTTP request
  return method == FormDataMethod.post
      ? _dio.post(endpoint, data: formData)
      : _dio.put(endpoint, data: formData);
}