Sending Image to Server and Uploading it to Firebase Cloud Storage

From Flutter → Node.js → Cloud Storage → back to Flutter

Soo Kim
5 min readMay 18, 2022

In this post, I’ll be discussing how I (with the help of endless googling) sent an image file from Flutter to the server (Node.js), uploaded it to Cloud Storage using Firebase Admin SDK, and retrieved the file back to Flutter.

Quick Summary

  1. Use Image Picker
  2. Send path of photo (String) to server
  3. Upload to Cloud Storage using await bucket.upload() & save destination file name to database
  4. Retrieve file using await bucket.file().download().
  5. Receive the file (which would be buffer array of integers), convert it to Uint8List and display it on Flutter using Image.memory.

Image Picker

To get access to the user’s gallery or camera, I’ve used flutter.dev’s image_picker. The process for using image picker is quite straight forward.

import 'package:image_picker/image_picker.dart';
Image Service

When user takes a photo/selects photos from gallery, I save the path(s) of the file in the relevant provider. When the new file paths get saved and relevant parts get notified, the page is updated with the photos.

ImageService _imageService = ImageService();List<String> _newPhotos = [];
List<String> get newPhotos => [...this._newPhotos];
set newPhotos(List<String> l) => throw "error";
Future<void> takePhoto() async {
final XFile? _xFile = await this._imageService.takePhoto();
if (_xFile == null) return;
this._newPhotos.add(_xFile.path);
this.notifyListeners();
}

Future<void> selectPhotos() async {
final List<XFile>? _xFiles = await this._imageService.multipleImages();
if (_xFiles == null) return;
_xFiles.forEach((XFile x) => this._newPhotos.add(x.path));
this.notifyListeners();
}

As for displaying the images, use Image.file(File(<pathString>)). Instead of saving the paths, you could choose to save them as Files to begin with, but I use the path String to identify which item from _newPhotos should be deleted…hence saving just the paths (which also will be sent to the server).

View

Sending Image to Server

When I was googling, it appeared that for flutter, a lot of people used MultiRequest, which is part of the http package, and for Node.js, multer. But I do not believe it’s necessary for uploading files to Cloud Storage as all it requires is the file path String.

When I send a post request to upload the image, I attach the list of Strings (file path) to the body.

if (this._newPhotos.isNotEmpty) _body["filePaths"] = this._newPhotos;

Uploading to Firebase Cloud Storage

For uploading to Firebase Cloud Storage, I use the Firebase Admin SDK. In your Index.js (or whatever your entry point is called), make sure you’ve initialized the Admin SDK like so.

const admin = require("firebase-admin");admin.initializeApp({
projectId: "your-project-id",
credential: admin.credential.cert(serviceAccount),
databaseURL: "your-database-url",
storageBucket: "your-storage-bucket"
});

Note: highly private information — i.e. credential — should be stored in a separate folder (mine is called secret), and in your .gitignore file, add the folder (e.g. secret/).

This is the code for my post_controller, where I upload images attached to users’ posts. The uploadPhoto method is in my addPost and editPost functions and called when req.body.filePaths exists.

const functions = require("firebase-functions"),
admin = require("firebase-admin"),
uuid = require("uuid"),
{ getStorage } = require("firebase-admin/storage");

Explanation for uploadPhoto()

  1. Since the server is receiving a list of file path Strings, I had to use new Promise(). The promise resolves with const imageFileNames = {imageUid: fileName}. The resolved promise is added onto req.body, so that I can add/update the information onto my Realtime Database afterwards.
  2. Within the Promise, I upload the file using bucket.upload(). The required parameter stringPath is the received filePath. Then, I set the destination as the postUid/fileName. The fileName is the last array element taken after slicing the full path by /.
posts_controller.js — uploadPhoto()

As you can see below, the photos have been added with the desired postUid/fileName “path,” and the fileName has been added to my Realtime. The imageUid is irrelevant and I only set it up for the purpose of allocating a key to each fileName.

Retrieving the Image File

For my app, retrieving the image file works similarly as the above. The getImage method is called when post.images exists when retrieving a post.

Explanation for getImage()

  1. This method receives the fileNames and postUid as the parameter. It uses a similar process of Promise, but instead of bucket.upload(), it uses await bucket.file(<file name>).download();
  2. You’ll notice that I pushed file[0], which indicates that the file you get is a list. It’s a buffer array that looks like the image below when you print it.
post_controller.js — getImage()
downloaded file from bucket

Receiving the Image on Flutter

The buffer array from Node.js, when received on Flutter, looks like this:

So when you receive it, you need to do convert that buffer data to a list of Uint8List so that you can easily use Image.network(). Since any kind of list received via JSON is List<dynamic>, make sure to use List<T>.from() before you use the list. Here is how I convert my data.

Post Class — factory

I struggled with this process but after making it work, I’m not sure as to why I even struggled in the first place…Hope it helps!

--

--