Problem Description
- S3 upload is successful, and the image object appears in the S3 bucket.
- But when I try to view the uploaded image, it shows a broken image icon instead of the actual image.
- The Content-Type is correctly set to image/jpeg or image/png, so that doesn’t seem to be the issue.
- I'm using Expo ImagePicker to select images and upload them to S3.
- The exact same logic worked fine on web, but it's not working properly in my Expo project.
Here's the code for uploading images to S3:
import axios from 'axios'
import { ImagePickerAsset } from 'expo-image-picker'
const API_URL = process.env.EXPO_PUBLIC_APP_AWS_IMAGE_API
export const uploadService = {
getPresignedUrl: async () => {
try {
const response = await axios.get(`${API_URL}/routines/images`)
return response.data.url
} catch (error) {
console.error('Error requesting presigned URL:', error)
throw error
}
},
uploadImageToS3: async (presignedUrl: string, file: ImagePickerAsset) => {
try {
// ✅ Convert to Blob (instead of Base64)
const response = await fetch(file.uri)
const blob = await response.blob()
// ✅ Upload to S3 using PUT
await axios.put(presignedUrl, blob, {
headers: {
'Content-Type': blob.type || 'image/jpeg', // Default content type
},
})
console.log('S3 Upload Success:', presignedUrl)
return presignedUrl.split('?')[0] // ✅ Return the uploaded S3 URL
} catch (error) {
console.error('Error uploading to S3:', error)
throw error
}
},
uploadImageToServer: async (url: string, params: { storeId: string; routineId: string; userTaskId: string }) => {
try {
const response = await axios.post(
`${API_URL}/routines/images`,
{ url, ...params },
{ headers: { 'Content-Type': 'application/json' } },
)
return response.data
} catch (error) {
console.error('Error uploading to server:', error)
throw error
}
},
}
Image Picker & Upload logic
import { useState } from 'react'
import * as ImagePicker from 'expo-image-picker'
import { Alert, Platform, ActionSheetIOS } from 'react-native'
import { uploadService } from '../services/fileUpload'
import { router } from 'expo-router'
import { postMessage } from '../components/WebviewContainer'
import { requestCameraPermission } from '../services/camera'
import { ImagePickerAsset } from 'expo-image-picker'
interface FileState {
file: ImagePickerAsset | null
preview: string | null
}
interface UploadParams {
storeId: string
routineId: string
userTaskId: string
}
async function launchCamera() {
const { permission } = await requestCameraPermission()
if (permission === 'granted') {
const result = await ImagePicker.launchCameraAsync({
allowsEditing: true,
quality: 0.8,
})
return result
}
return null
}
async function launchGallery() {
const { permission } = await requestPhotoPermission()
if (permission === 'granted') {
const result = await ImagePicker.launchImageLibraryAsync({
quality: 0.8,
selectionLimit: 1,
allowsMultipleSelection: false,
})
return result
}
return null
}
export default function useFileUpload() {
const [fileState, setFileState] = useState<FileState>({ file: null, preview: null })
const [isLoading, setIsLoading] = useState(false)
const handleChange = async () => {
if (Platform.OS === 'ios') {
ActionSheetIOS.showActionSheetWithOptions(
{
options: ['Take a photo', 'Choose from gallery', 'Cancel'],
cancelButtonIndex: 2,
destructiveButtonIndex: 2,
},
async (buttonIndex) => {
if (buttonIndex === 0) {
const result = await launchCamera()
if (result.assets?.[0]) {
setFileState({ file: result.assets[0], preview: result.assets[0].uri })
}
} else if (buttonIndex === 1) {
const result = await launchGallery()
if (result.assets?.[0]) {
setFileState({ file: result.assets[0], preview: result.assets[0].uri })
}
}
},
)
} else {
Alert.alert(
'Select Image',
'Choose how you want to upload the image',
[
{ text: 'Take a photo', onPress: async () => {
const result = await launchCamera()
if (result?.assets?.[0]) {
setFileState({ file: result.assets[0], preview: result.assets[0].uri })
}
}},
{ text: 'Choose from gallery', onPress: async () => {
const result = await launchGallery()
if (result?.assets?.[0]) {
setFileState({ file: result.assets[0], preview: result.assets[0].uri })
}
}},
{ text: 'Cancel', style: 'cancel' }
],
{ cancelable: true }
)
}
}
const handleUpload = async (params: UploadParams) => {
if (!fileState.file) {
return null
}
try {
setIsLoading(true)
const preSignedUrl = await uploadService.getPresignedUrl()
await uploadService.uploadImageToS3(preSignedUrl, fileState.file)
const s3FileURL = preSignedUrl.split('?')[0]
await uploadService.uploadImageToServer(s3FileURL, params)
setIsLoading(false)
postMessage('uploadImage', { isUploaded: true, url: s3FileURL, ...params })
router.back()
} catch (error) {
console.error('Error uploading file:', error)
setIsLoading(false)
postMessage('uploadImage', { isUploaded: false, url: '', ...params })
throw error
}
}
return {
fileState,
isLoading,
handleChange,
handleUpload,
}
}
What I've tried:
- Confirmed that S3 upload succeeds.
- Checked that Content-Type is correctly set (image/jpeg or image/png).
- The exact same logic worked on web, but it's failing on Expo.
- Tried uploading the file as a Base64 string, but that didn’t work either.
- Converted file.uri to Blob before uploading, still no luck.
Why is the uploaded image broken?
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744856855a4597458.html
评论列表(0条)