Expo and uploading image Blobs?

This answer is going back to the very first of this question which is like 4 month before. We know it is working fine with amazon s3 or other backends. What we want is using firebase storage!!!

All of these people were suffering for this for long time and still suffering today.

We really need solution for this.

Hi! Upstream RN merged blob support recently, should be available in a release within 1-2 months!

4 Likes

@js1599 I like the solution you proposed. However when using putString, I’m getting the following error: Dropbox - File Deleted
Is that something that you’ve encountered as well?

Is there a way we can use that now?

Blob support for network requests is still in progress (see https://github.com/facebook/react-native/pull/11573). One of the open-source RN maintainers at Facebook says:

We’re still planning on landing this. I can’t give a specific ETA - this is a pretty large PRs and integrating it with our codebase (including ensuring it passes all tests, which are not exposed here) has taken longer than expected.

Once that lands, it will be 1-2 months before it makes its way to a final RN release and we include it in Expo.

In this small tutorial, I wrote a cloud function that you can use to upload images to Firebase with Expo: Uploading Images to Firebase with Expo | by William Candillon | Medium. I hope this helps.

3 Likes

Awesome! Thank you for sharing this, William. :+1:t4:

I tried this solution today and there is error in firebase multipart requests. Anyone tried? I am really looking to solutions for this one for long time.

I updated the solution at Uploading Images to Firebase with Expo | by William Candillon | Medium to provide the necessary middlewares required in order to process the multipart request. It should work nicely for you.

Thanks for quick reply. Have you tried this? I’ve tried this already like below but this solution won’t work for me. File is always undefined.
Error message: 2017-12-09T14:16:44.513Z E app: TypeError: Cannot read property ‘originalname’ of undefined

const admin = require('firebase-admin');
const functions = require('firebase-functions');
const express = require('express');
const multer = require('multer');
const upload = multer();
const bodyParser = require('body-parser');
const getRawBody = require('raw-body');
const contentType = require('content-type');
const Busboy = require('busboy');

admin.initializeApp(functions.config().firebase);

const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: true
}));

app.use((req, res, next) => {
  if (req.rawBody === undefined && req.method === "POST" && req.headers["content-type"].startsWith("multipart/form-data")) {
    getRawBody(req, {
      length: req.headers["content-length"],
      limit: "10mb",
      encoding: contentType.parse(req).parameters.charset
    }, function(err, string) {
      if (err) return next(err);
      req.rawBody = string;
      next();
    });
  } else {
    next();
  }
});

app.use((req, res, next) => {
  if (req.method === "POST" && req.headers["content-type"].startsWith("multipart/form-data")) {
    const busboy = new Busboy({
      headers: req.headers
    });
    let fileBuffer = new Buffer("");
    req.files = {
      file: []
    };

    busboy.on("file", (fieldname, file, filename, encoding, mimetype) => {
      file.on("data", (data) => {
        fileBuffer = Buffer.concat([fileBuffer, data]);
      });

      file.on("end", () => {
        const file_object = {
          fieldname,
          originalname: filename,
          encoding,
          mimetype,
          buffer: fileBuffer
        };

        req.files.file.push(file_object);
        next();
      });
    });

    busboy.end(req.rawBody);
  } else {
    next();
  }
});

app.post('/picture', upload.single('picture'), function(req, response, next) {
  console.log('picture');
  console.log(req.file);
  uploadImageToStorage(req.file)
    .then((uri) => {
      response.status(200).json(uri);
      next();
    })
    .catch((error) => {
      console.error(error);
      response.status(500).json({ error });
      next();
    });
});

exports.app = functions.https.onRequest(app);

const uploadImageToStorage = (file) => {
  return new Promise((resolve, reject) => {
    const fileUpload = admin.storage().bucket().file(file.originalname);
    const blobStream = fileUpload.createWriteStream({
      metadata: {
        contentType: 'image/jpg'
      }
    });

    blobStream.on('error', (error) => reject(error));

    blobStream.on('finish', () => {
      // The public URL can be used to directly access the file via HTTP.
      resolve({
        [file.originalname]:
        `https://storage.googleapis.com/<BUCKET NAME>.appspot.com/${file.originalname}`
      });
    });

    blobStream.end(file.buffer);
  });
};

Sorry I forgot to update index.js. You can remove multer.

api.post("/picture", function (req, response, next) {
    uploadImageToStorage(req.files.file[0])

I’m updating the blog post as well

1 Like

Thanks!!! Finally it is working!!! By the way do you have any idea to fix below problem?

[eslint] 'new Buffer()' was deprecated since v6. Use 'Buffer.alloc()' or 'Buffer.from()' (use 'https://www.npmjs.com/package/safe-buffer' for '<4.5.0') instead. (node/no-deprecated-api)

Hi. I am stucked for 6 hours now…

I’ve successfully uploaded file but can never get the download URL. Above download url example did not worked for me so I’ve been googling to solve the problem. I’ve found a lot out there but couldn’t solve my problem. Have you tried your download url to load in your react-native app?

my last related stackoverflow link is below.

I get the download URL client side, it works pretty well. Could the issue
be that you are trying to get the download url server side?

Nah I just can’t load image with url provided below.
https://storage.googleapis.com/.appspot.com/${file.originalname}

This is how I do it client side:

            const body = new FormData();
            const name = `${id}.jpg`;
            // $FlowFixMe
            body.append("picture", {
                uri: picture.uri,
                name,
                type: "image/jpg"
            });
            await fetch(`${Firebase.endpoint}/picture`, {
                method: "POST",
                body,
                headers: {
                    Accept: "application/json",
                    "Content-Type": "multipart/form-data"
                }
            });
            const url = await Firebase.storage.ref(name).getDownloadURL();
1 Like

AH… This can be another solution. Should have known this earlier. I’ve figreued out the other way like below. Google’s doc is so frustrating… So hard to find the working example.

const uploadImageToStorage = (file) => {
  return new Promise((resolve, reject) => {
    let uuid = UUID();
    const imgPath = decodeURIComponent(file.originalname).replace('%2E', /\./g);
    const blob = gcsBucket.file(imgPath);
    const bucket = blob.createWriteStream({
      metadata: {
        contentType: 'image/png',
        metadata: {
          firebaseStorageDownloadTokens: uuid,
        }
      }
    });

    bucket.on('error', (error) => reject(error));

    bucket.on('finish', () => {
      console.log('finished');
      blob.getSignedUrl({
        action: 'read',
        expires: '03-09-2491'
      }).then((signedUrls) => {
        console.log('signedUrls');
        console.log(signedUrls[0]);
        resolve({ url: signedUrls[0] });
      });
    });

    bucket.end(file.buffer);
  });
};
TypeError: handler is not a function
    at cloudFunction (/user_code/node_modules/firebase-functions/lib/providers/https.js:26:41)
    at /var/tmp/worker/worker.js:671:7
    at /var/tmp/worker/worker.js:655:9
    at _combinedTickCallback (internal/process/next_tick.js:73:7)
    at process._tickDomainCallback (internal/process/next_tick.js:128:9)

did anybody else get this error? I followed the medium article but it didn’t work? Did I setup the cloud function wrong? I didn’t get any errors when I uploaded it?

I solved the issue as I had an index.js file, you have to use module.exports = api.post etc. if you want to separate the logic for multiple google cloud functions

hello, I want to try this on my project, is there any working example of this code?

1 Like