Reputation: 11
I am developing a React Native application using Expo and Supabase. I need to upload images to a Supabase Storage bucket. I am using Expo's ImagePicker to select images, and I want to upload these images directly to Supabase Storage using the @supabase/supabase-js library.
Despite following various tutorials and examples, the image is not being uploaded to the bucket, and I receive no specific error messages to debug the issue. After selecting the image on my phone and clicking "Choose," nothing happens—the image does not get uploaded, and no error is displayed.
I have ensured that my Supabase bucket policies allow public uploads and access.
Here is the current code for the upload functionality:
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;
And i used the following policies:
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');
Upvotes: 1
Views: 688
Reputation: 21
If you read the supabase documentation here
You cannot use a blob to upload in react native. You have to upload an ArrayBuffer
from base64 file data instead.
You could do something like this:
An example using 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/*",
});
Upvotes: 2