Uploading a File to a Server from Flutter Using a Multi-Part (form-data) POST Request
Tags: android, ios, dart, node
Published: 2020-02-02 20:56:04

My Flutter book is pretty light on advanced HTTP networking topics, focusing instead on giving a more well-rounded approach that, when it comes to networking, explains how to use the http networking package for basic requests, shows an example of an app that makes GET requests, and then goes a bit more specific with Firebase.

My blog is here for those who need more than that, and the first topic I'm going to cover is how to upload a file using a multi-part/form-data POST request.

You can find all of the code contained in this example in [this GitHub repository](https://github.com/carzacc/form-data-post).

## What is a Multi-Part POST Request

Usually, the body of a POST request is made of textual key-value pairs. With a multipart POST request, you can also include files with binary content (images, various documents, etc.), in addition to the regular text values.

## Making Multi-Part POST Requests with Flutter's HTTP Library

This little section is going to focus on one thing: creating a function that, given a file path and an URL, is able to send that file to that URL with that multipart POST request.

The next section is going to focus on how to build an app that lets the user insert the URL to which to send the request, pick an image and send it to the server, getting back the status of the request.

This means we're not going to worry about dependencies in this section. All you need to know for this section is that we've got this at the top of our `main.dart`:

~~~
import 'package:http/http.dart' as http;
~~~

This allows us to *create* (not send) a multipart POST request using

~~~
var req = http.MultipartRequest('POST', Uri.parse(url));
~~~

This `req` object has a member `Map` called `fields` for textual values and a `List` called `files` to which you can add `MultipartFiles`.

### The MultipartFile

The most important element of this whole thing is the `MultipartFile`. It can be constructed in a few ways:

* the default `MultipartFile(key, stream, length)` constructor, which you can use if you need to create the file from a `Stream` of bytes of which we know the length;
* the `MultipartFile.fromBytes(key, bytes)` factory method, which attaches a file obtained from a `List` of bytes;
* the `MultipartFile.fromString(key, string)` factory method, which attaches a text file containing the string passed to it;
* the `MultipartFile.fromPath(key, path)` factory method, which attaches the file found at the given path.

In case you’re wondering, *bytes* simply means *integers*. That’s all a byte is. An 8-bit integer value. A list of integers, coupled with an encoding (that can usually be inferred with the help of the extension that’s part of the file name) is all you need to get images, documents, videos, music or text.

If you're using one of the first two constructors for the `MultipartFile`, remember to set a filename using the specific named `filename` argument, as not doing that will cause issues with our back-end.

Here are a few examples: if you have a file and a path to it, here’s how you create a multipart POST request and attach that file to the request:

* Using the default constructor you could write a function like the following:

{% gist https://gist.github.com/carzacc/4037d367611b995d505927c7fd16835b %} 

* Using`MultipartFile.fromBytes()` you could write a function like the following:

{% gist https://gist.github.com/carzacc/e6d35fe65f4d4078323d0216a7870f71 %} 

* Using`MultipartFile.fromPath()` you could write a function like the following:

{% gist https://gist.github.com/carzacc/f86551ee0de4ba18997686dd77ed08c0 %} 

### Adding Regular Text Fields

You could add regular text fields to a Multipart POST requests simply by adding, after settomg initialized the `MultipartRequest` object, an item in the `fields` member `Map`:

~~~
var request = http.MultipartRequest('POST', Uri.parse(url));
request.fields['key'] = 'value';
~~~

## Building an App Around That

Now, to see it in action, let's write an app that allows the user to insert an URL, pick an image, and then upload the image to the server at that URL. We'll write the very simple back-end code required to accept files this way in Node after we're done with the app. Feel free to skip ahead if you're more interested in that.

Let's start, as we must, with an evaluation of the dependencies and the `pubspec.yaml` file. We'll use two packages from Dart Pub: the `image_picker` Flutter package that will handle the image selection dialog for us, about which [I wrote a tutorial on Medium a year ago (not part of the metered paywall in case you're wondering)](https://medium.com/@carminezaccagnino/taking-and-or-using-pictures-in-flutter-apps-using-the-image-picker-package-b8e366654a0) and the [Dart `http` package](https://pub.dev/packages/http) that provides the networking features we need.

Add both the dependencies in `pubspec.yaml` (check the GitHub repo I linked at the start of the post if you don't know how to do that) and import them both into the `lib/main.dart` file along with the usual Flutter Material Design API:

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

### The Start Page

Now let's think about the actions the user needs to take in our app: in order to upload images, we first need an URL, so let's show a screen that allows the user to set the URL first, and then we allow them to upload images. We'll call this first screen `StartPage`. This is the usual Flutter code used to initialize an app, this tutorial really is meant for those who don't need much explanation as to what it does and why (that's more the realm of introductory tutorials and books like the one I've written):

{% gist https://gist.github.com/carzacc/f07e674086d9092c4e9197e03c72db7b %} 

We use a `TextEditingController` to let the user both submit the value from the Android keyboard or press our cusdtom `FlatButton` below the `TextField`.

### The Upload Page

Here's the code for the upload page, I'll explain it down below after the code:

{% gist https://gist.github.com/carzacc/b592ac6630b6158bcaefd955b9a50509 %} 

`uploadImage()` is the simplest of the functions we saw earlier to upload images: we use `MultipartFile.fromPath()` directly and return the status string of the request. That's going to be the `state` variable, which is what is shown in the middle of the screen to the user. The URL is the one we get from the `StartPage`.

The `FloatingActionButton` is fairly simple: `ImagePicker.pickImage()` shows the familiar image picking screen to the user returns (through a `Future`) the path to the file the user picks.

We use that (as part of the floating action button's  `onPressed` callback) together with the URL passed by the `StartPage` to call the `uploadImage` function and re-render the view using `setState` to show the `state` of the request to the user.

## Writing a Simple Back-End in Node to Handle Multi-Part POST Requests

For this section, the tools we're going to use are Node, Express and Multer. There's not going to be much code involved, this is a fairly simple affair.

First of all, install Node and NPM according to the instructions for your operating system on [Node’s official website](https://nodejs.org/en/).

After that, create a directory, run the command

~~~
$ npm init
~~~

and fill out to your preference the choices given to you (the default values are fine in case of doubt)

and then

~~~
$ npm --save install express multer
~~~

Create a file called `index.js` with the following content:

{% gist https://gist.github.com/carzacc/22cfe51227660d02aa6cf3e44d4e3555 %} 

As you can see it's not much code, but I'm going to explain it nonetheless.

The first 4 lines are simply dealing with dependencies: the first three are straight-up imports of dependencies, whereas line 4 sets up the Multer middleware (the one that handles file uploads) to save uploaded files to the `uploads` directory.

Line 6 instantiates an Express server object, as the Express API documentation requires.

### Creating an Express Rule for Handling Multipart POST requests

After that, on lines 8 to 19, we define a rule for incoming POST requests to the `/upload` path (in other words, it responds to POST requests sent to `<server IP>:<port>/upload`), it takes one file on the `picture` key, and it fires the callback function we defined as `(req, res) {...}` in the lines 8 to 19.

The callback prints to the console the name of the file that was received (on line 9), it then renames the file to the original file name.

It does that on lines 10 to 16 by creating, as is done using Node's `fs` interface, streams (much like `FILE*` variables in C) that allow our script to read from an input file (`req.file.path` is the path Multer saved the received file to) and write to an output file (using the passed `req.file.originalname`, which is the filename specified in the POST request by the sender, which is our Flutter app). When this is finished (lines 13 to 16) we delete (`unlinkSync`) the temporary file created by Multer (which has a name that ensures there aren't two files with the same name but doesn't even keep the same extension as the original file) and send back a response saying everything was OK.

If an error happens (often because no file name was specified, on line 17) we will inform the sender of that.

On lines 22 and 23 we start the server on the port specified by the `PORT` environment variable or on port 3000 if no port was specified using environment variables.

That's it.

## Making Our Example Work

To use the example code, start the server by running `node index.js`. Then, fire up the Flutter app on a phone/emulator, get your PC's local IP (if the phone is connected to the same local network or it's running on an emulator) to substitute to `<server_ip>` in `http://<server_ip>:3000/upload`, which is what you need to type into the `TextField` in the app. For example, if your PC's IP is `192.168.1.2`, you'd type in `http://192.168.1.2:3000/upload`. After that, press the `+` icon, pick an image, and you'll see the Node script will print out a message like `Received image_picker<gibberish>.jpg`. By opening the `uploads` subdirectory in the directory where you have the back-end Node script, you'll find a file called `image_picker<gibberish>.jpg`.

I hope you found this tutorial useful and I invite you to tell me what topic I should cover next.