Image Picker
Using expo-image-picker to select images from the gallery, compress them, and upload to your backend.
Image Picker
expo-image-picker lets users select photos from their device gallery or take new photos. ScaleRocket Mobile uses it for avatar uploads, post images, and document attachments.
Setup
npx expo install expo-image-pickerAdd permission descriptions to app.json:
{
"expo": {
"ios": {
"infoPlist": {
"NSPhotoLibraryUsageDescription": "We access your photos to let you upload a profile picture"
}
}
}
}Pick an Image
import * as ImagePicker from "expo-image-picker";
async function pickImage() {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [1, 1],
quality: 0.8,
});
if (!result.canceled) {
const image = result.assets[0];
console.log("Selected:", image.uri);
return image;
}
}| Option | Value | Purpose |
|---|---|---|
mediaTypes | Images, Videos, All | Filter media type |
allowsEditing | true | Show crop/resize UI |
aspect | [1, 1] | Crop aspect ratio (iOS only) |
quality | 0.0 - 1.0 | Image compression |
Take a Photo
Use launchCameraAsync to open the camera directly:
async function takePhoto() {
const permission = await ImagePicker.requestCameraPermissionsAsync();
if (!permission.granted) {
Alert.alert("Permission required", "Camera access is needed to take photos");
return;
}
const result = await ImagePicker.launchCameraAsync({
allowsEditing: true,
aspect: [1, 1],
quality: 0.8,
});
if (!result.canceled) {
return result.assets[0];
}
}Image Compression
Compress images before uploading to reduce bandwidth and storage:
import { manipulateAsync, SaveFormat } from "expo-image-manipulator";
async function compressImage(uri: string) {
const result = await manipulateAsync(
uri,
[{ resize: { width: 800 } }],
{ compress: 0.7, format: SaveFormat.JPEG }
);
return result.uri;
}npx expo install expo-image-manipulatorUpload to Backend
Upload to Supabase Storage:
import { supabase } from "@/lib/supabase";
async function uploadImage(uri: string, userId: string) {
const compressedUri = await compressImage(uri);
const fileName = `${userId}/avatar-${Date.now()}.jpg`;
const formData = new FormData();
formData.append("file", {
uri: compressedUri,
name: "avatar.jpg",
type: "image/jpeg",
} as any);
const { data, error } = await supabase.storage
.from("avatars")
.upload(fileName, formData, { upsert: true });
if (error) throw error;
const { data: urlData } = supabase.storage
.from("avatars")
.getPublicUrl(fileName);
return urlData.publicUrl;
}Upload to Convex File Storage:
import { useMutation } from "convex/react";
import { api } from "@/convex/_generated/api";
async function uploadImage(uri: string) {
const compressedUri = await compressImage(uri);
// Get upload URL
const uploadUrl = await generateUploadUrl();
// Upload the file
const response = await fetch(compressedUri);
const blob = await response.blob();
const result = await fetch(uploadUrl, {
method: "POST",
body: blob,
headers: { "Content-Type": "image/jpeg" },
});
const { storageId } = await result.json();
return storageId;
}Complete Avatar Upload Component
function AvatarUpload({ user }: { user: User }) {
const [uploading, setUploading] = useState(false);
const [avatarUri, setAvatarUri] = useState(user.avatarUrl);
const handleUpload = async () => {
const image = await pickImage();
if (!image) return;
setUploading(true);
try {
const url = await uploadImage(image.uri, user.id);
setAvatarUri(url);
} catch (error) {
Alert.alert("Upload Failed", "Could not upload image. Try again.");
} finally {
setUploading(false);
}
};
return (
<TouchableOpacity onPress={handleUpload} disabled={uploading}>
<Avatar source={avatarUri ? { uri: avatarUri } : undefined} name={user.name} size="lg" />
{uploading && <ActivityIndicator style={styles.overlay} />}
</TouchableOpacity>
);
}Multiple Image Selection
Select multiple images at once:
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsMultipleSelection: true,
selectionLimit: 5,
quality: 0.8,
});
if (!result.canceled) {
const images = result.assets; // Array of selected images
}Notes
allowsEditingonly supports single image selection- On iOS,
aspectratio is only enforced whenallowsEditingis true - Large images should always be compressed before upload
- Image picker works in Expo Go (unlike camera)