Photos from the gallery and camera
Phone features such as the camera, location, contacts and vibration are available through Expo modules. Each module is a package with JavaScript functions backed by native iOS and Android code. Let us look at expo-image-picker: the user picks an avatar for the blog profile.
Installing
npx expo install expo-image-pickerThe module is already built into Expo Go, so it works right after installing the package.
Picking from the gallery
// app/(tabs)/profile.tsx
import { Image } from 'expo-image';
import * as ImagePicker from 'expo-image-picker';
import { useState } from 'react';
import { Button, View } from 'react-native';
export default function Profile() {
const [avatar, setAvatar] = useState<string | null>(null);
async function pickImage() {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ['images'],
allowsEditing: true,
aspect: [1, 1],
quality: 0.8,
});
if (!result.canceled) {
setAvatar(result.assets[0].uri);
}
}
return (
<View style={{ flex: 1, alignItems: 'center', padding: 16, gap: 12 }}>
{avatar && <Image source={avatar} style={{ width: 120, height: 120, borderRadius: 60 }} />}
<Button title="Choose photo" onPress={pickImage} />
</View>
);
}Tap "Choose photo" and the system gallery opens. After you pick a photo, a crop screen to a square appears (aspect: [1, 1]), and then a round avatar above the button. uri is the file address on the phone; you can show it in an Image or upload it to a server.
Options
| Option | What it sets |
|---|---|
mediaTypes | What to pick: ['images'], ['videos'] or both |
allowsEditing | Show the crop screen |
aspect | Crop proportions, for example [4, 3] |
quality | Compression quality from 0 to 1 |
If the user closed the gallery, result.canceled is true and there are no assets.
Taking a photo
The camera needs the user's permission. It is requested before taking a photo:
// app/(tabs)/profile.tsx
async function takePhoto() {
const permission = await ImagePicker.requestCameraPermissionsAsync();
if (!permission.granted) {
Alert.alert('No access to the camera');
return;
}
const result = await ImagePicker.launchCameraAsync({ allowsEditing: true, aspect: [1, 1] });
if (!result.canceled) {
setAvatar(result.assets[0].uri);
}
}Add a <Button title="Take photo" onPress={takePhoto} /> button and Alert to the import. On the first press the system asks for permission, then the camera opens. The iOS Simulator has no camera, so test on a phone.
The permission message
The text of the system dialog is set in app.json through the module's plugin:
{
"expo": {
"plugins": [
["expo-image-picker", { "cameraPermission": "The camera is used to take a profile photo." }]
]
}
}This text goes into the app when it is built with EAS; Expo Go shows its own standard text.
Common mistake
Not checking result.canceled and reading result.assets[0].uri right away. If the user closed the gallery without choosing, assets is null, and the app crashes. Always check for cancellation before using the result.