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.
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.
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 most important element of this whole thing is the MultipartFile
. It can be constructed in a few ways:
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;MultipartFile.fromBytes(key, bytes)
factory method, which attaches a file obtained from a List
of bytes;MultipartFile.fromString(key, string)
factory method, which attaches a text file containing the string passed to it;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:
MultipartFile.fromBytes()
you could write a function like the following:MultipartFile.fromPath()
you could write a function like the following: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';
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) and the Dart http
package 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';
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):
We use a TextEditingController
to let the user both submit the value from the Android keyboard or press our cusdtom FlatButton
below the TextField
.
Here’s the code for the upload page, I’ll explain it down below after the code:
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.
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.
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:
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.
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.
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.