Mastering Image Uploads in Flutter using Multipart Requests

Sending and receiving data is something to consider when building web and mobile applications when establishing connections to the internet. Flutter luckily provides multiple libraries that can help you handle these requests like dio, retrofit, HTTP among others.

The HTTP package is among many API request packages currently existing in the flutter world. The most common type of HTTP requests are the GET and the POST methods. Today we will talk about making a multipart post request using the HTTP flutter package.

What is a multipart request?

A multipart request, within the context of HTTP, is a post request typically used for file uploads and transferring data of several types in a single request. Imagine sending an image file alongside a JSON object – this is where multipart requests shine. It is a type of HTTP request that allows the client construct files and send these files as data to the server.

Setting up the project

Before we make a dive into the implementation of the image upload functionality, you need the following:

  1. Install the HTTP package and the image picker package.
//in pubspec.yaml file in your flutter application 

image_picker: ^1.0.2 // you can change to the current latest version available
http: ^0.13.4

Once the dependencies have been resolved, then we import the packages into our widget.

//Import the packages in your widget
import 'dart:io';
import 'package:image_picker/image_picker.dart';
import 'package:http/http.dart' as http;

Well, we know the HTTP package is for getting and posting data to and from the server but let's see what the image picker does.

The image picker is a flutter plugin for iOS and Android for picking images from the image library and taking new pictures with the camera.

Capturing Images with the ImagePicker

To make use of this library we'll make use of functions that deal with the user interaction.

  1. Getting an already existing image from the local storage.

     void _pickFile() async {
         final pickedFile = await ImagePicker().pickMedia(
           maxWidth: 1800,
           maxHeight: 1800,
         );
         // if no file is picked
         if (pickedFile == null) return;
         final File pickedImageFile = File(pickedFile.path);
         setState(() {
           _image = pickedImageFile;
         });
       }
    
  2. Getting an image taken from the camera.

       Future<void> _getImageFromCamera() async {
         var image = await ImagePicker().pickImage(source: ImageSource.camera);
         if (image != null) {
           setState(() {
             _image = File(image.path);
           });
         }
    

These functions allow users to select an image from their device's gallery or capture a new picture using the camera. The selected or captured image is then stored in the _image variable.

Submitting the file to the post URL

Now let's send the captured or selected image to a server that accepts a multipart/form-data request.

Include the URL you want to post the selected image/File data by creating an asynchronous function.

Future<dynamic> _submitRequest(
      {required File image, //image/file to be submitted
        required String type}) async {
    var url = "url to post to"; // url that accepts the mutlipart values.

    var request = http.MultipartRequest("POST", Uri.parse(url))
      ..headers["Content-type"] = "multipart/form-data"
      ..fields["any field"] = "value1" //add any extra field you want
      ..fields["any field"] = jsonEncode("object") // if the field is an object or array, encode the field
      ..files.add(await http.MultipartFile.fromPath('file', image.path,
          filename: 'file.jpg'));

    var response = await request.send();
    var responseString = await response.stream
        .bytesToString(); //this returns the returned object as a string

    if (response.statusCode == 200 || response.statusCode == 201) {
      //this is your action when the submit is successful
      return {
        "success": true,
        "message": "Submitted properly",
        "data" : json.decode(responseString), //what the server returns
      };
    } else {
      //this runs once the response isn't a status code of 200
      return {"success": false, "message": "An error occured during transfer"};
    }
  }

Here the MultipartRequest Method is implemented from the HTTP package.The function takes in the image as a file and uses the MulipartRequest method to make a post request with a multipart/form-data as the content type. Once the request is completed the function returns a Map depending on the statusCode the response returns.

Conclusion

In this article, we've explored how to implement image capturing and image uploading in a Flutter application using the ImagePicker and the HTTP package to send the files to a server using the Multipart POST requests. This functionality is particularly useful for apps that require user-generated image content.

Below is a sample widget with a basic UI implementing all the needed functions:

import 'dart:convert';
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:image_picker/image_picker.dart';
import 'package:http/http.dart' as http;

class UploadImage extends StatefulWidget {
  const UploadImage({Key? key}) : super(key: key);

  @override
  State<UploadImage> createState() => _UploadImageState();
}

class _UploadImageState extends State<UploadImage> {
//File is from the dart.io package
  File _image = new File("");
  bool setLoading = false;

  void _pickFile() async {
    final pickedFile = await ImagePicker().pickMedia(
      maxWidth: 1800,
      maxHeight: 1800,
    );
    // if no file is picked
    if (pickedFile == null) return;
    final File pickedImageFile = File(pickedFile.path);
    setState(() {
      _image = pickedImageFile;
    });
  }

  Future<void> _getImageFromCamera() async {
    var image = await ImagePicker().pickImage(source: ImageSource.camera);
    if (image != null) {
      setState(() {
        _image = File(image.path);
      });
    }
  }

  Future<dynamic> _submitRequest(
      {required File image, required String type}) async {
    setState(() {
      setLoading = true;
    });
    var url = "url to post to"; // url that accepts the mutlipart values.

    var request = http.MultipartRequest("POST", Uri.parse(url))
      ..headers["Content-type"] = "multipart/form-data"
      ..fields["any field"] = "value1" //add any field you want
      ..files.add(await http.MultipartFile.fromPath('file', image.path,
          filename: 'file.jpg'));

    var response = await request.send();
    var responseString = await response.stream
        .bytesToString(); //this returns the returned object as a string

    setState(() {
      setLoading = false;
    });
    if (response.statusCode == 200 || response.statusCode == 201) {
      //this is your action when the submit is successful
      return {
        "success": true,
        "message": "Submitted properly",
        "data": json.decode(responseString),
      };
    } else {
      //this runs once the response isn't a status code of 200
      return {"success": false, "message": "An error occured during transfer"};
    }
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Container(
        padding: EdgeInsets.all(20.0),
        child: SingleChildScrollView(
          scrollDirection: Axis.vertical,
          physics: BouncingScrollPhysics(),
          child: Column(
            children: [
              Center(
                child: Row(
                  children: [
                    Expanded(
                      flex: 1,
                      child: ElevatedButton(
                        onPressed: () {
                          _pickFile();
                        },
                        child: Text("Select image from Gallery",
                            textAlign: TextAlign.center,
                            style: TextStyle(fontSize: 14.0)),
                      ),
                    ),
                    SizedBox(
                      width: 10,
                    ),
                    Expanded(
                      flex: 1,
                      child: ElevatedButton(
                        onPressed: () {
                          _getImageFromCamera();
                        },
                        child: Text("Take image with Camera",
                            textAlign: TextAlign.center),
                      ),
                    ),
                  ],
                ),
              ),
              Column(
                children: [
                  SizedBox(
                    height: 20,
                  ),
                  _image.path.isNotEmpty
                      ? Container(
                          child: Image.file(_image),
                        )
                      : SizedBox.shrink(),
                  SizedBox(
                    height: 20,
                  ),
                  _image.path.isNotEmpty
                      ? (!setLoading
                          ? ElevatedButton(
                              onPressed: () {
                                _submitRequest(image: _image, type: "type");
                              },
                              child: Text("Upload image",
                                  textAlign: TextAlign.center),
                            )
                          : CircularProgressIndicator(
                              color: Colors.white,
                            ))
                      : SizedBox.shrink(),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}
0
Subscribe to my newsletter

Read articles from Abdullah Ola Mudathir directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Abdullah Ola Mudathir
Abdullah Ola Mudathir