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)
- https://firebase.google.com/docs/storage/web/upload-files (ofc)
- https://www.youtube.com/watch?v=KkZckepfm2Q (too old/using RNF)
- https://github.com/choru-k/React-Native-Tips/tree/master/How_to_upload_photo%2Cfile_in%20react-native (too old/using RNF)
- Convert image path to blob react native (mostly focused on RNF)
- React Native Blob fetch throws error: Failed to construct 'Response': The status provided (0) is outside the range [200, 599] (this one helped for the Android simulator upload, but fails on physical device as well)
- Convert image path to blob react native
- Expo Camera Photo Upload To Firebase Storage is undefined – React Native (already included the solution here – maybe outdated?)
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