image

16 Feb 2026

9K

35K

Flutter & Dio: Uploading Images and Files with Multipart Form

File uploads are a common requirement in many modern applications, from user profile pictures to document sharing. In Flutter, handling network requests efficiently and robustly is crucial. Dio is a powerful HTTP client for Dart that simplifies complex tasks like file uploads, especially when dealing with multipart form data. This article will guide you through the process of uploading images and general files from a Flutter application to a server using Dio and the multipart/form-data content type.

Prerequisites

Before diving in, ensure you have:
  • Flutter SDK installed and configured.
  • Basic understanding of Flutter widget tree and state management.
  • An active internet connection.
  • A backend endpoint configured to accept multipart file uploads (e.g., Node.js with Multer, Flask with request.files, etc.). For this article, we'll assume such an endpoint exists and is listening for POST requests at a specific URL.

Setting Up Dio and File Picker

First, add dio and file_picker (to select files from the device) to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  dio: ^5.0.0 # Use the latest stable version
  file_picker: ^6.1.1 # Use the latest stable version
After adding them, run flutter pub get in your project directory.

Understanding Multipart Form Data

When you need to send files along with other textual data (like user IDs, descriptions, etc.) in a single HTTP request, multipart/form-data is the standard content type. It allows you to encapsulate different parts (each representing a field or a file) within a single request body, separated by boundaries. Dio provides convenient ways to construct this type of request.

Implementing File Upload in Flutter

Let's create a simple Flutter application with a button to pick a file and another to upload it.

1. Create the UI

We'll use a StatefulWidget to manage the selected file and upload state.

import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import 'package:file_picker/file_picker.dart';
import 'dart:io';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter File Upload',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State createState() => _MyHomePageState();
}

class _MyHomePageState extends State {
  File? _selectedFile;
  double _uploadProgress = 0;
  final Dio _dio = Dio(); // Create a Dio instance

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('File Upload with Dio'),
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(20.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              _selectedFile == null
                  ? const Text('No file selected.')
                  : Text('Selected file: ${_selectedFile!.path.split('/').last}'),
              const SizedBox(height: 20),
              ElevatedButton(
                onPressed: _pickFile,
                child: const Text('Pick File'),
              ),
              const SizedBox(height: 20),
              _selectedFile != null
                  ? ElevatedButton(
                      onPressed: _uploadFile,
                      child: const Text('Upload File'),
                    )
                  : Container(),
              const SizedBox(height: 20),
              _uploadProgress > 0 && _uploadProgress < 1
                  ? LinearProgressIndicator(value: _uploadProgress)
                  : Container(),
              _uploadProgress == 1
                  ? const Text('Upload complete!')
                  : Container(),
              _uploadProgress == -1 // Custom value for error
                  ? const Text('Upload failed!', style: TextStyle(color: Colors.red))
                  : Container(),
            ],
          ),
        ),
      ),
    );
  }

  // ... (methods will be added here)
}

2. Picking a File

We'll use the file_picker package to allow users to select any type of file.

  Future _pickFile() async {
    FilePickerResult? result = await FilePicker.platform.pickFiles(
      type: FileType.any, // You can specify FileType.image, FileType.video, etc.
    );

    if (result != null && result.files.single.path != null) {
      setState(() {
        _selectedFile = File(result.files.single.path!);
        _uploadProgress = 0; // Reset progress when a new file is picked
      });
    } else {
      // User canceled the picker
      setState(() {
        _selectedFile = null;
        _uploadProgress = 0;
      });
    }
  }

3. Constructing FormData and Uploading

This is the core logic where Dio comes into play. We'll create a FormData object, add our file, and any other data, then send it via a POST request.

  Future _uploadFile() async {
    if (_selectedFile == null) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Please select a file first.')),
      );
      return;
    }

    setState(() {
      _uploadProgress = 0; // Start progress at 0
    });

    try {
      String fileName = _selectedFile!.path.split('/').last;
      FormData formData = FormData.fromMap({
        "file": await MultipartFile.fromFile(
          _selectedFile!.path,
          filename: fileName,
        ),
        "description": "This is a file uploaded from Flutter.", // Example of adding other fields
        "userId": 123,
      });

      // Replace with your actual backend endpoint URL
      String uploadUrl = "YOUR_BACKEND_UPLOAD_URL_HERE"; 

      Response response = await _dio.post(
        uploadUrl,
        data: formData,
        onSendProgress: (received, total) {
          if (total != -1) {
            setState(() {
              _uploadProgress = received / total;
              print('Upload progress: ${_uploadProgress * 100}%'); // For debugging
            });
          }
        },
      );

      if (response.statusCode == 200 || response.statusCode == 201) {
        setState(() {
          _uploadProgress = 1; // Mark as complete
          _selectedFile = null; // Clear selected file after successful upload
        });
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('File uploaded successfully!')),
        );
        print("File uploaded successfully: ${response.data}");
      } else {
        setState(() {
          _uploadProgress = -1; // Mark as failed
        });
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('File upload failed: ${response.statusCode}')),
        );
        print("File upload failed with status: ${response.statusCode}");
      }
    } on DioException catch (e) {
      setState(() {
        _uploadProgress = -1; // Mark as failed
      });
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Upload error: ${e.message}')),
      );
      print("Upload error: $e");
    } catch (e) {
      setState(() {
        _uploadProgress = -1; // Mark as failed
      });
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('An unexpected error occurred: $e')),
      );
      print("An unexpected error occurred: $e");
    }
  }

Important Notes for Backend

Ensure your backend is configured to accept POST requests to the specified uploadUrl.
  • The backend should expect the file under the field name "file" (matching what you set in FormData.fromMap).
  • It should also be able to parse multipart/form-data and extract other fields like "description" and "userId".
  • Example for Node.js with express and multer (install with npm install express multer):

const express = require('express');
const multer = require('multer');
const path = require('path');
const app = express();
const port = 3000;

// Set up storage for uploaded files
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'uploads/') // Files will be saved in the 'uploads/' directory
  },
  filename: function (req, file, cb) {
    cb(null, Date.now() + '-' + file.originalname) // Unique filename
  }
});

const upload = multer({ storage: storage });

app.post('/upload', upload.single('file'), (req, res) => {
  if (!req.file) {
    return res.status(400).send('No file uploaded.');
  }
  console.log('File received:', req.file);
  console.log('Other fields:', req.body); // Access other fields like description, userId
  res.status(200).json({ 
    message: 'File uploaded successfully!', 
    filename: req.file.filename,
    description: req.body.description,
    userId: req.body.userId
  });
});

app.listen(port, () => console.log(`Server running on http://localhost:${port}/`));

Advanced Considerations

  • Multiple File Uploads: You can add multiple MultipartFile instances to the FormData object with different field names or an array of files if your backend supports it.
  • Request Cancellation: Dio allows you to cancel ongoing requests using CancelToken.
  • Error Handling: Implement more granular error handling based on status codes or specific Dio exceptions.
  • Loading States: Enhance the UI with proper loading indicators, disabling buttons during upload, etc.
  • File Type Validation: Before uploading, you can add client-side validation for file types and sizes.

Conclusion

Uploading images and files in Flutter using Dio and multipart form data is a straightforward and robust process. By leveraging file_picker for selecting files and Dio's FormData for constructing the request, you can efficiently send various types of files along with additional data to your backend. Remember to configure your backend endpoint correctly to handle these multipart requests. This setup provides a solid foundation for integrating file upload capabilities into your Flutter applications.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is