October 21, 2024
Chicago 12, Melborne City, USA
Android

Uploade photo via Expo to Firebase storage (Firebase JS SDK) not working (blob conversion)


I’m not sure what else to try and need some help with uploading a photo taken with expo-camera on a physical Android device running Expo Go to be uploaded to Firebase storage (emulated for now) using Firebase JS SDK.

I’ve seen many threads on this, but most are either outdated or using the React Native Firebase instead of the JS SDK. Since I need web to work as well, I would love to stick to JS.

This works perfectly fine with many of the approaches you will see below on web.

Without further ado, here’s my code. Please! Let me know how I correctly convert the blob and upload it (I don’t care whether with uploadBytes, uploadBytesResumable or uploadString…

Camera.tsx

const takePhoto = async () => {
    const photo = await cameraRef.current?.takePictureAsync({
      quality: 0.5,
      base64: true,
    })
    if (!photo) {
      console.error('No photo taken')
      return
    }
    console.log(photo)
    const uploadResult = await uploadPhoto(photo)
    console.log(uploadResult)
  }

api/uploadPhoto.ts

import { SaveFormat, manipulateAsync } from 'expo-image-manipulator'
import * as FileSystem from 'expo-file-system'
import uuid from 'react-native-uuid'
import {
  ref,
  uploadBytesResumable,
  getDownloadURL,
  uploadBytes,
  StorageReference,
  FirebaseStorage,
  uploadString,
} from 'firebase/storage'
import { FIREBASE_DB, FIREBASE_STORAGE } from '@/utils/firebaseConfig'
import { addDoc, collection, serverTimestamp } from 'firebase/firestore'
const MAX_FILE_SIZE_MB = 1

export default async function uploadPhoto(photo) {
  console.log('Received photo', photo)
  // Create a storage reference
  const storage = FIREBASE_STORAGE
  const storageRef = ref(storage, `photos/${uuid.v4()}`)
  console.log('storageRef', storageRef)
  // Get uri from Photo
  const uri = await getUriFromPhoto(photo)
  console.log('Photo uri', uri)
  try {
    // Fetch file
    const file = await fetch(uri.replace('file:///', 'file:/'))
    console.log('file', file)
    // Compress file
    const compressedFile = await compressFile(uri)
    console.log('compressedFile', compressedFile)
    //Check if file is smaller than 1MB
    const smallerThanMaxSize = await checkSizeIsLessThan(
      compressedFile.uri,
      MAX_FILE_SIZE_MB
    )
    if (!smallerThanMaxSize) {
      throw new Error('Image is too large')
    } else {
      console.log('File is smaller than 1MB')
    }
    //Create blob from file
    const fetchedCompressedFile = await fetch(
      compressedFile.uri.replace('file:///', 'file:/')
    )
    console.log('fetchedCompressedFile', fetchedCompressedFile)
    // const blob1 = await uriToBlob(fetchedCompressedFile.uri)
    // console.log('blob1', blob1)
    // const blob2 = await createBlobFromUriXhr(compressedFile)
    // console.log('blob2', blob2)
    // const blob3 = await createBlobFromUriWorkaround(compressedFile)
    // console.log('blob3', blob3)
    // Upload file and get download URL
    // const downloadUrl = await uploadBlob(storageRef, blob1, {
    //   contentType: 'image/jpeg',
    // })
    // console.log('downloadUrl', downloadUrl)
    return
    // Add URL to Firestore
    // const id = await addDownloadUrlToFirestore(photo.filename, downloadUrl)
  } catch (uploadError) {
    console.error('Error uploading bytes:', uploadError)
  }
}

async function uploadImageAsync(uri) {
  // Why are we using XMLHttpRequest? See:
  // https://github.com/expo/expo/issues/2402#issuecomment-443726662
  const storage = FIREBASE_STORAGE
  const blob = await new Promise<Blob>((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.onload = function () {
      resolve(xhr.response as Blob)
    }
    xhr.onerror = function (e) {
      console.log(e)
      reject(new TypeError('Network request failed'))
    }
    xhr.responseType="blob"
    xhr.open('GET', uri, true)
    xhr.send(null)
  })

  const storageRef = ref(storage, `photos/${uuid.v4()}`)
  const snapshot = await uploadBytes(storageRef, blob)

  return await getDownloadURL(snapshot.ref)
}
async function getUriFromPhoto(photo) {
  const uri = photo.uri
  return uri
}

async function fetchFile(uri: string) {
  const response = await fetch(uri)

  if (!response.ok) {
    throw new Error(
      `Failed to fetch file from uri: ${uri}: response.statusText`
    )
  }
  return response
}

async function compressFile(uri: string) {
  try {
    const result = await manipulateAsync(
      uri,
      [
        {
          resize: {
            width: 800,
          },
        },
      ],
      {
        format: SaveFormat.JPEG,
        base64: true,
        compress: 0.1,
      }
    )
    console.log('Reduced file result:', result)
    return result
  } catch (error) {
    console.error('Error compressing file:', error)
    throw error
  }
}

async function checkSizeIsLessThan(
  uri: string,
  maxSizeMb: number
): Promise<boolean> {
  const fileInfo = await FileSystem.getInfoAsync(uri)
  if (!fileInfo.exists) {
    throw new Error(`File does not exist at uri: ${uri}`)
  }
  return fileInfo.size! < maxSizeMb * 1024 * 1024
}

async function createBlobFromUri(uri: string): Promise<Blob> {
  try {
    const response = await fetch(uri)
    const blob = await response.blob()
    console.log('createBlobFromUri blob', blob)
    return blob
  } catch (error) {
    console.error('Failed to create blob from URI', error)
    throw error
  }
}

/**
 * Function to convert a URI to a Blob object
 * @param {string} uri - The URI of the file
 * @returns {Promise} - Returns a promise that resolves with the Blob object
 */
export function uriToBlob(uri: string): Promise<Blob> {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()

    // If successful -> return with blob
    xhr.onload = function () {
      resolve(xhr.response)
    }

    // reject on error
    xhr.onerror = function () {
      reject(new Error('uriToBlob failed'))
    }

    // Set the response type to 'blob' - this means the server's response
    // will be accessed as a binary object
    xhr.responseType="blob"

    // Initialize the request. The third argument set to 'true' denotes
    // that the request is asynchronous
    xhr.open('GET', uri, true)

    // Send the request. The 'null' argument means that no body content is given for the request
    xhr.send(null)
  })
}

async function createBlobFromUriXhr(uri: string): Promise<Blob> {
  console.log('createBlobViaXhrAsync uri', uri)

  const blob = await new Promise<Blob>((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.onload = function () {
      resolve(xhr.response as Blob)
    }
    xhr.onerror = function (e) {
      console.log(e)
      reject(new TypeError('Network request failed'))
    }
    xhr.responseType="blob"
    xhr.open('GET', uri, true)
    xhr.send(null)
  })
  console.log('createBlobViaXhrAsync blob', blob)
  return blob
}

async function createBlobFromUriWorkaround(uri: string): Promise<Blob> {
  const originalUri = uri
  const fileName = uri.substring(uri.lastIndexOf('/') + 1)
  // Workaround see https://github.com/facebook/react-native/issues/27099
  const newUri = `${FileSystem.documentDirectory}resumableUploadManager-${fileName}.toupload`
  await FileSystem.copyAsync({ from: originalUri, to: newUri })
  const response = await fetch(newUri)
  const blobData = await response.blob()
  const blob = new Blob([blobData], { type: 'image/jpeg' })
  console.log('createBlobFromUriWorkaround blob', blob)
  return blob
}

async function uploadBlob(
  storageRef: StorageReference,
  blob: Blob,
  metadata?: any
): Promise<string> {
  const uploadBytesResponse = await uploadBytes(storageRef, blob, metadata)
  console.log('uploadBytesResponse', uploadBytesResponse)
  try {
    const uploadTask = uploadBytesResumable(storageRef, blob, metadata)
    return new Promise((resolve, reject) => {
      uploadTask.on(
        'state_changed',
        (snapshot) => {
          const progress =
            (snapshot.bytesTransferred / snapshot.totalBytes) * 100
          console.log('Upload is ' + progress + '% done')
          switch (snapshot.state) {
            case 'paused':
              console.log('Upload is paused')
              break
            case 'running':
              console.log('Upload is running')
              break
          }
        },
        (error) => {
          console.error('Error uploading bytes:', error)
          reject(error)
        },
        () => {
          console.log('Upload is complete')
          getDownloadURL(uploadTask.snapshot.ref)
            .then((downloadURL) => {
              console.log('File available at', downloadURL)
              resolve(uploadTask.snapshot.ref.fullPath)
            })
            .catch((error) => {
              console.error('Error getting download URL:', error)
              reject(error)
            })
        }
      )
    })
  } catch (error) {
    console.error('Error uploading bytes:', error)
    throw error
  }
}

async function addDownloadUrlToFirestore(
  fileName: string,
  downloadURL: string
) {
  try {
    const docRef = await addDoc(collection(FIREBASE_DB, 'photos'), {
      fileName,
      downloadURL,
      createdAt: serverTimestamp(),
    })
    console.log('Document written with ID: ', docRef.id)
    return docRef.id
  } catch (error) {
    console.error('Error adding document: ', error)
    throw error
  }
}

And now a list of resources I used or posts I tried (as you will see in my code)

I also tried this on a physical iOS device, same result (none).



You need to sign in to view this answers

Leave feedback about this

  • Quality
  • Price
  • Service

PROS

+
Add Field

CONS

+
Add Field
Choose Image
Choose Video