Expo Modules
Use this skill when working with Expo's extensive SDK modules for accessing device features and native functionality.
Key Concepts
Camera
import { Camera, CameraType } from 'expo-camera'; import { useState } from 'react'; import { Button, View } from 'react-native';
export default function CameraScreen() { const [permission, requestPermission] = Camera.useCameraPermissions(); const [type, setType] = useState(CameraType.back);
if (!permission?.granted) { return ( <View> <Button title="Grant Permission" onPress={requestPermission} /> </View> ); }
return ( <Camera style={{ flex: 1 }} type={type}> <Button title="Flip Camera" onPress={() => setType(type === CameraType.back ? CameraType.front : CameraType.back) } /> </Camera> ); }
Location
import * as Location from 'expo-location'; import { useEffect, useState } from 'react';
export function useLocation() { const [location, setLocation] = useState<Location.LocationObject | null>(null);
useEffect(() => { (async () => { const { status } = await Location.requestForegroundPermissionsAsync(); if (status !== 'granted') return;
const loc = await Location.getCurrentPositionAsync({});
setLocation(loc);
})();
}, []);
return location; }
Notifications
import * as Notifications from 'expo-notifications'; import { useEffect } from 'react';
Notifications.setNotificationHandler({ handleNotification: async () => ({ shouldShowAlert: true, shouldPlaySound: true, shouldSetBadge: false, }), });
export function useNotifications() { useEffect(() => { const subscription = Notifications.addNotificationReceivedListener( (notification) => { console.log(notification); } );
return () => subscription.remove();
}, []);
const sendNotification = async () => { await Notifications.scheduleNotificationAsync({ content: { title: 'Hello!', body: 'This is a notification', }, trigger: { seconds: 2 }, }); };
return { sendNotification }; }
File System
import * as FileSystem from 'expo-file-system';
export async function saveFile(data: string, filename: string) {
const uri = ${FileSystem.documentDirectory}${filename};
await FileSystem.writeAsStringAsync(uri, data);
return uri;
}
export async function readFile(filename: string) {
const uri = ${FileSystem.documentDirectory}${filename};
const content = await FileSystem.readAsStringAsync(uri);
return content;
}
export async function downloadFile(url: string, filename: string) {
const uri = ${FileSystem.documentDirectory}${filename};
const download = await FileSystem.downloadAsync(url, uri);
return download.uri;
}
Image Picker
import * as ImagePicker from 'expo-image-picker'; import { useState } from 'react'; import { Button, Image, View } from 'react-native';
export default function ImagePickerScreen() { const [image, setImage] = useState<string | null>(null);
const pickImage = async () => { const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, allowsEditing: true, aspect: [4, 3], quality: 1, });
if (!result.canceled) {
setImage(result.assets[0].uri);
}
};
return ( <View> <Button title="Pick Image" onPress={pickImage} /> {image && <Image source={{ uri: image }} style={{ width: 200, height: 200 }} />} </View> ); }
Best Practices
Permission Handling
import * as Location from 'expo-location';
async function requestLocationPermission() { const { status: foregroundStatus } = await Location.requestForegroundPermissionsAsync();
if (foregroundStatus !== 'granted') { console.log('Permission denied'); return false; }
// Request background permission only if needed const { status: backgroundStatus } = await Location.requestBackgroundPermissionsAsync();
return backgroundStatus === 'granted'; }
Secure Storage
import * as SecureStore from 'expo-secure-store';
export async function saveToken(key: string, value: string) { await SecureStore.setItemAsync(key, value); }
export async function getToken(key: string) { return await SecureStore.getItemAsync(key); }
export async function deleteToken(key: string) { await SecureStore.deleteItemAsync(key); }
Device Info
import * as Device from 'expo-device'; import * as Application from 'expo-application'; import Constants from 'expo-constants';
export function getDeviceInfo() { return { deviceName: Device.deviceName, deviceType: Device.deviceType, osName: Device.osName, osVersion: Device.osVersion, appVersion: Application.nativeApplicationVersion, buildVersion: Application.nativeBuildVersion, expoVersion: Constants.expoVersion, }; }
Common Patterns
Persistent Storage
import AsyncStorage from '@react-native-async-storage/async-storage';
export const storage = { async set(key: string, value: any) { await AsyncStorage.setItem(key, JSON.stringify(value)); },
async get<T>(key: string): Promise<T | null> { const item = await AsyncStorage.getItem(key); return item ? JSON.parse(item) : null; },
async remove(key: string) { await AsyncStorage.removeItem(key); },
async clear() { await AsyncStorage.clear(); }, };
Background Tasks
import * as BackgroundFetch from 'expo-background-fetch'; import * as TaskManager from 'expo-task-manager';
const BACKGROUND_FETCH_TASK = 'background-fetch';
TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => { // Do work here console.log('Background task running'); return BackgroundFetch.BackgroundFetchResult.NewData; });
export async function registerBackgroundTask() { return BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK, { minimumInterval: 60 * 15, // 15 minutes stopOnTerminate: false, startOnBoot: true, }); }
Sharing Content
import * as Sharing from 'expo-sharing';
export async function shareContent(uri: string) { const isAvailable = await Sharing.isAvailableAsync();
if (!isAvailable) { console.log('Sharing is not available'); return; }
await Sharing.shareAsync(uri, { mimeType: 'image/jpeg', dialogTitle: 'Share this image', }); }
Related Skills
-
expo-config: Configuring module permissions
-
expo-build: Including modules in builds