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.
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 {% raw %}`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:
- Using
MultipartFile.fromBytes()
you could write a function like the following:
- Using
MultipartFile.fromPath()
you could write a function like the following:
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 {% raw %}`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 {% raw %}`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
.
The Upload Page
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.
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.
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{% raw %}
$ npm --save install express multer
Create a file called {% raw %}`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.
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.
Top comments (12)
Hey, nice tutorial, but I have an issue with the http connection.
It says:
I added
to my AndroidManifest, but it does not work.
Any solutions?
You can try this solution: flutter.dev/docs/release/breaking-...
This works for me
Hey, please help me, after try 3 method, i always get this error
I/flutter (10831): │ 🐛 NoSuchMethodError: The getter 'length' was called on null.
I/flutter (10831): │ 🐛 Receiver: null
I/flutter (10831): │ 🐛 Tried calling: length
/data/user/0/com.navy.flutter_test_upload/cache/image_picker3454028650704276924.jpg
Thanks, very good article
I use nodejs on windows 10 and couldn't figure it out how to send request to local server, I think it was about firewall
1)Make network Private and 2)create outbound rule in windows firewall for port 3000 on my IPv4 address which deal with local IP address of development machine (my desktop)
3) navigate to with android chrome to 192.168.43.79:3000/upload and code works fine
Hi Carmine thanks for the article. It's useful for me!
Is this working on flutter web?
I get "Error: Unsupported operation: _Namespace" when trying to do File(filename).readAsBytes().asStream(),
Hi Carmine
This article is very useful for me. And then, You used node.js. I would like to use asp.net WCF service. If you have code sample with asp.net WCF service,can you share me,please?.
Very usefull.....well done!!!
How can i upload file with custom name to files?
Hi, thanks for this good article. Can you please guide me on how to upload multiple images using multipart apis?
Nice article, but why is it so terribly formatted? You really should not post text for reading in the code blocks. Reading with horizontal scrolling is a pain.
request.fields['key'] = 'value'; if value is in integer then how can we pass