通过 Expo 将照片上传到 Firebase 存储(Firebase JS SDK)不起作用(blob 转换)

问题描述 投票:0回答:1

我不确定还可以尝试什么,并且需要一些帮助来上传在运行 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 设备上尝试过此操作,结果相同(无)。

android react-native expo firebase-storage expo-go
1个回答
0
投票

发现我的问题:

我无法导入 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)
}
© www.soinside.com 2019 - 2024. All rights reserved.