我不确定还可以尝试什么,并且需要一些帮助来上传在运行 Expo Go 的 物理 Android 设备上使用 expo-camera 拍摄的照片,以便上传到 Firebase 存储(目前已模拟)使用 Firebase JS SDK。
我见过很多关于此的线程,但大多数要么已经过时,要么使用 React Native Firebase 而不是 JS SDK。由于我也需要 Web 来工作,所以我愿意坚持使用 JS。
这与您将在下面在网络上看到的许多方法完美配合。
事不宜迟,这是我的代码。 请!让我知道如何正确转换 blob 并上传它(我不在乎是否使用 uploadBytes、uploadBytesResumable 或 uploadString...
相机.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
}
}
现在是我使用的资源或我尝试过的帖子的列表(正如您将在我的代码中看到的那样)
我也在物理 iOS 设备上尝试过此操作,结果相同(无)。
发现我的问题:
我无法导入 Firebase 配置,因此 Firebase 未初始化。 只需将导入添加到我的根 _layout.tsx 文件
import '@/utils/firebaseConfig'
并能够解决一些小问题。此外,当涉及到您正在使用的平台时,Firebase Storage 会出现一些奇怪的行为。主机需要进行不同的设置。但不幸的是,这在不同平台上并不一致。因此,通过模拟器的 Android 需要通过物理设备使用不同的 Android 主机(10.0.2.2 vs localhost vs 主机 IP(例如 192.168.1.10)...并且不会出现任何错误消息。工作代码:
import { Platform } from 'react-native'
import uuid from 'react-native-uuid'
import { manipulateAsync, SaveFormat } from 'expo-image-manipulator'
import * as FileSystem from 'expo-file-system'
import { ref, uploadBytes, getDownloadURL, getStorage } from 'firebase/storage'
import { FIREBASE_STORAGE, FIREBASE_DB } from '@/utils/firebaseConfig'
import { addDoc, collection, serverTimestamp } from 'firebase/firestore'
export default async function uploadImage(image) {
const maxFileSize = parseFloat(
process.env.EXPO_PUBLIC_MAX_IMAGE_SIZE_MB || '1'
)
console.log('Received image to handle', image)
const manipulatedImage = await _manipulateImage(image.uri)
if (
Platform.OS !== 'web' &&
!(await _checkSizeIsLessThan(manipulatedImage.uri, maxFileSize))
) {
throw new Error('Image size is too large')
}
try {
return await _uploadImageAsync(manipulatedImage.uri)
} catch (e) {
throw new Error(e)
}
}
async function _manipulateImage(uri) {
const resizeWidth = parseFloat(
process.env.EXPO_PUBLIC_DEFAULT_IMAGE_WIDTH || '1024'
)
const compression =
parseFloat(process.env.EXPO_PUBLIC_DEFAULT_IMAGE_COMPRESSION) || 1
const manipulatedImageResult = await manipulateAsync(
uri,
[{ resize: { 'width': resizeWidth } }],
{
compress: compression,
format: SaveFormat.JPEG,
}
)
console.log('Image manipulated', manipulatedImageResult)
return manipulatedImageResult
}
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 _uploadImageAsync(uri) {
console.log('Received uri to upload', uri)
// Why are we using XMLHttpRequest? See:
// https://github.com/expo/expo/issues/2402#issuecomment-443726662
const blob = await new Promise<Blob>((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.onload = function () {
resolve(xhr.response)
}
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('Blob created', blob)
const fileRef = ref(FIREBASE_STORAGE, `photos/${uuid.v4()}`)
console.log('File reference created', fileRef)
const result = await uploadBytes(fileRef, blob)
console.log('Filed uploaded', result)
return await getDownloadURL(fileRef)
}