我正在使用 Expo 和 Supabase 开发 React Native 应用程序。我需要将图像上传到 Supabase 存储桶。我正在使用 Expo 的 ImagePicker 来选择图像,并且我想使用 @supabase/supabase-js 库将这些图像直接上传到 Supabase Storage。
尽管遵循了各种教程和示例,但图像并未上传到存储桶,并且我没有收到任何具体的错误消息来调试问题。在我的手机上选择图像并单击“选择”后,没有任何反应 - 图像没有上传,也没有显示错误。
我已确保我的 Supabase 存储桶策略允许公开上传和访问。
这是上传功能的当前代码:
import React, { useState } from 'react';
import {
View,
Text,
TextInput,
TouchableOpacity,
StyleSheet,
SafeAreaView,
ScrollView,
Image,
Alert,
} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { useNavigation, useRoute } from '@react-navigation/native';
import * as ImagePicker from 'expo-image-picker';
import { supabase } from '../database/supabaseClient';
import 'react-native-url-polyfill/auto';
import { Upload } from '@supabase/supabase-js';
const UploadSurpriseBagScreen = () => {
const navigation = useNavigation();
const route = useRoute();
const { userId } = route.params || {};
const [name, setName] = useState('');
const [bagNumber, setBagNumber] = useState('');
const [pickupHour, setPickupHour] = useState('');
const [validation, setValidation] = useState('');
const [price, setPrice] = useState('');
const [description, setDescription] = useState('');
const [category, setCategory] = useState('Breakfast');
const [imageUri, setImageUri] = useState('');
const [uploading, setUploading] = useState(false);
const pickImage = async () => {
const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (permissionResult.granted === false) {
alert("You've refused to allow this app to access your photos!");
return;
}
const pickerResult = await ImagePicker.launchImageLibraryAsync({
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
if (!pickerResult.cancelled) {
setImageUri(pickerResult.uri);
return pickerResult;
}
};
const uploadImage = async (uri) => {
try {
setUploading(true);
const response = await fetch(uri);
const blob = await response.blob();
const fileExt = uri.split('.').pop();
const fileName = `${Date.now()}.${fileExt}`;
const filePath = `${fileName}`;
const { data, error } = await supabase.storage.from('surprise_bags').upload(filePath, blob);
if (error) {
throw error;
}
const { publicURL, error: publicURLError } = await supabase.storage.from('surprise_bags').getPublicUrl(filePath);
if (publicURLError) {
throw publicURLError;
}
return publicURL;
} catch (error) {
console.error('Error uploading image:', error);
alert('Error uploading image');
return null;
} finally {
setUploading(false);
}
};
const handleUploadBag = async () => {
if (!name || !bagNumber || !pickupHour || !validation || !price || !description || !imageUri) {
alert('Please fill out all fields and upload an image');
return;
}
const imageUrl = await uploadImage(imageUri);
if (!imageUrl) return;
const { data: shop, error: shopError } = await supabase
.from('shops')
.select('id')
.eq('employee_id', userId)
.single();
if (shopError) {
console.error('Error fetching shop:', shopError);
alert('Error fetching shop information');
return;
}
const { error } = await supabase
.from('surprise_bags')
.insert([
{
employee_id: userId,
shop_id: shop.id,
name,
bag_number: bagNumber,
pickup_hour: pickupHour,
validation,
price,
description,
category,
image_url: imageUrl,
},
]);
if (error) {
console.error('Error uploading surprise bag:', error);
alert('Error uploading surprise bag');
} else {
alert('Surprise bag uploaded successfully');
navigation.goBack();
}
};
return (
<SafeAreaView style={styles.container}>
<TouchableOpacity onPress={() => navigation.goBack()} style={styles.backButton}>
<Icon name="arrow-left" size={24} color="#000" />
</TouchableOpacity>
<ScrollView contentContainerStyle={styles.scrollContainer}>
<Text style={styles.headerTitle}>Upload Surprise Bags</Text>
<TouchableOpacity style={styles.uploadPhotoContainer} onPress={async () => {
const response = await pickImage();
if (response?.uri) {
setImageUri(response.uri);
}
}}>
{imageUri ? (
<Image source={{ uri: imageUri }} style={styles.uploadedImage} />
) : (
<>
<Icon name="image-outline" size={50} color="#82866b" />
<Text style={styles.uploadPhotoText}>Upload Photo</Text>
</>
)}
</TouchableOpacity>
<Text style={styles.label}>Name</Text>
<TextInput
style={styles.input}
placeholder="e.g. Surprise Bag"
value={name}
onChangeText={setName}
/>
<Text style={styles.label}>Bag no.</Text>
<TextInput
style={styles.input}
placeholder="e.g. #001"
value={bagNumber}
onChangeText={setBagNumber}
/>
<Text style={styles.label}>Pick up Hour</Text>
<TextInput
style={styles.input}
placeholder="e.g. 12:30pm - 4:30am"
value={pickupHour}
onChangeText={setPickupHour}
/>
<Text style={styles.label}>Validation</Text>
<TextInput
style={styles.input}
placeholder="e.g. 07/02/24 - 09/02/24"
value={validation}
onChangeText={setValidation}
/>
<Text style={styles.label}>Price</Text>
<TextInput
style={styles.input}
placeholder="e.g. $3.50"
value={price}
onChangeText={setPrice}
/>
<Text style={styles.label}>What you could get</Text>
<TextInput
style={styles.input}
placeholder="e.g. Lorem ipsum dolor sit amet consectetur."
value={description}
onChangeText={setDescription}
/>
<Text style={styles.label}>Food Category</Text>
<View style={styles.pickerContainer}>
<TouchableOpacity
onPress={() => setCategory('Breakfast')}
style={[styles.pickerButton, category === 'Breakfast' && styles.pickerButtonSelected]}
>
<Text style={styles.pickerButtonText}>Breakfast</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => setCategory('Lunch')}
style={[styles.pickerButton, category === 'Lunch' && styles.pickerButtonSelected]}
>
<Text style={styles.pickerButtonText}>Lunch</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => setCategory('Dinner')}
style={[styles.pickerButton, category === 'Dinner' && styles.pickerButtonSelected]}
>
<Text style={styles.pickerButtonText}>Dinner</Text>
</TouchableOpacity>
</View>
<TouchableOpacity style={styles.uploadButton} onPress={handleUploadBag} disabled={uploading}>
<Text style={styles.uploadButtonText}>{uploading ? 'Uploading...' : 'Upload Bag'}</Text>
</TouchableOpacity>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#FFFFFF',
},
backButton: {
marginTop: 10,
marginLeft: 10,
},
scrollContainer: {
paddingHorizontal: 20,
},
headerTitle: {
fontSize: 26,
fontWeight: 'bold',
textAlign: 'center',
marginTop: 30,
marginBottom: 20,
},
uploadPhotoContainer: {
borderColor: '#676a61',
borderWidth: 1,
borderStyle: 'dashed',
borderRadius: 10,
padding: 20,
alignItems: 'center',
marginBottom: 20,
},
uploadedImage: {
width: 100,
height: 100,
borderRadius: 10,
},
uploadPhotoText: {
fontSize: 16,
color: '#5c5f4c',
textAlign: 'center',
marginVertical: 10,
},
label: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 5,
color: '#5c5f4c',
},
input: {
borderWidth: 1,
borderColor: '#000',
paddingVertical: 8,
paddingHorizontal: 10,
borderRadius: 5,
marginBottom: 20,
height: 50,
},
pickerContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 20,
},
pickerButton: {
flex: 1,
paddingVertical: 10,
alignItems: 'center',
borderWidth: 1,
borderColor: '#000',
borderRadius: 5,
marginHorizontal: 5,
backgroundColor: '#fff',
},
pickerButtonSelected: {
backgroundColor: '#82866b',
},
pickerButtonText: {
color: '#5c5f4c',
},
uploadButton: {
backgroundColor: '#82866b',
paddingVertical: 12,
borderRadius: 5,
alignItems: 'center',
marginBottom: 20,
},
uploadButtonText: {
color: '#fff',
fontSize: 18,
fontWeight: 'bold',
},
});
export default UploadSurpriseBagScreen;
我使用了以下政策:
CREATE POLICY "Anyone can upload to surprise_bags"
ON storage.objects
FOR INSERT
WITH CHECK (bucket_id = 'surprise_bags');
CREATE POLICY "Anyone can update surprise_bags"
ON storage.objects
FOR UPDATE
USING (bucket_id = 'surprise_bags')
WITH CHECK (bucket_id = 'surprise_bags');
CREATE POLICY "Anyone can view surprise_bags"
ON storage.objects
FOR SELECT
USING (bucket_id = 'surprise_bags');
如果您阅读了supabase文档这里 您不能使用 blob 在 React Native 中上传。您必须从 base64 文件数据上传
ArrayBuffer
。
你可以这样做:
使用 expo 的示例
import * as FileSystem from "expo-file-system";
import { decode } from "base64-arraybuffer";
// Read the file as a Base64-encoded string using Expo's FileSystem
const base64 = await FileSystem.readAsStringAsync(uri, {
encoding: FileSystem.EncodingType.Base64,
});
// Decode the Base64 string to an ArrayBuffer
const arrayBuffer = decode(base64);
// Upload the image to Supabase Storage
const { data, error } = await supabase.storage
.from(bucketName) // Replace with your storage bucket name
.upload(fileName, arrayBuffer, {
upsert: true,
contentType: "image/*",
});