Complete solution (100% reference)
index.html- Main HTML pagescript.js- JavaScript logicstyles.css- Styling and layoutpackage.json- Dependenciessetup.sh- Setup scriptREADME.md- Instructions (below)๐ก Download the ZIP, extract it, and follow the instructions below to get started!
65% Pre-Built Template for Learning Image Handling in React Native
By completing this activity, you will:
Total Time: 60 minutes Difficulty: Intermediate Concept: Concept 04 - Working with Images
# 1. Navigate to template folder
cd activity-04-camera-snap
# 2. Install dependencies
npm install --legacy-peer-deps
# 3. Start Expo development server
npx expo start
# 4. Open on your device with Expo Go app
# Scan the QR code that appears in your terminal
โ ๏ธ Important: Camera features require a physical device. The iOS Simulator and Android Emulator can only access the photo library, not the camera.
This template comes partially complete with working components you can use immediately:
ImagePreview Component (components/ImagePreview.js)
PermissionPrompt Component (components/PermissionPrompt.js)
ActionButton Component (components/ActionButton.js)
Image Helper Utilities (utils/imageHelpers.js)
Learning Goal: Understand how to request device permissions properly
File to Edit: App.js (line ~50)
What to Implement:
const requestCameraPermission = async () => {
try {
// 1. Request permission from user
const { status } = await ImagePicker.requestCameraPermissionsAsync();
// 2. Check if granted
if (status === 'granted') {
return true; // Permission granted!
} else {
// 3. Handle denial
setShowPermissionPrompt(true);
Alert.alert(
'Camera Permission Needed',
'Please enable camera access in Settings to take photos',
[{ text: 'OK' }]
);
return false;
}
} catch (error) {
console.error('Permission error:', error);
Alert.alert('Error', 'Failed to request camera permission');
return false;
}
};
Key Concepts:
await){ status } property'granted', 'denied', 'undetermined'Success Criteria:
true when permission grantedfalse when permission deniedLearning Goal: Learn to use expo-image-picker to capture photos
File to Edit: App.js (line ~85)
What to Implement:
const handleTakePhoto = async () => {
try {
// Step 1: Check permission first
const hasPermission = await requestCameraPermission();
if (!hasPermission) {
return; // Permission denied, exit early
}
// Step 2: Launch camera with configuration
const result = await ImagePicker.launchCameraAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true, // Shows crop screen after capture
aspect: [4, 3], // Camera aspect ratio
quality: 0.8, // 0-1 scale, 0.8 = good quality + smaller file
});
// Step 3: Check if user took photo (didn't cancel)
if (!result.canceled && result.assets && result.assets.length > 0) {
// Step 4: Extract image URI and optimize
const imageUri = result.assets[0].uri;
const optimizedUri = optimizeImageUri(imageUri);
setSelectedImage(optimizedUri);
}
} catch (error) {
console.error('Camera error:', error);
Alert.alert('Error', 'Failed to take photo. Please try again.');
}
};
Key Concepts:
launchCameraAsync() returns { canceled, assets } objectresult.canceled === false means user took photoresult.assets[0].uri (not result.uri)allowsEditing: true shows crop/edit screen (better UX)quality: 0.8 balances quality and file sizeSuccess Criteria:
Learning Goal: Apply camera skills to photo library selection
File to Edit: App.js (line ~120)
What to Implement:
const handleChoosePhoto = async () => {
try {
// Step 1: Request library permission
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (status !== 'granted') {
Alert.alert(
'Permission Needed',
'Please enable photo library access in Settings'
);
return;
}
// Step 2: Launch photo library
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [4, 3],
quality: 0.8,
});
// Step 3: Handle result (same as camera)
if (!result.canceled && result.assets && result.assets.length > 0) {
const imageUri = result.assets[0].uri;
const optimizedUri = optimizeImageUri(imageUri);
setSelectedImage(optimizedUri);
}
} catch (error) {
console.error('Library error:', error);
Alert.alert('Error', 'Failed to select photo');
}
};
Key Concepts:
handleTakePhotoSuccess Criteria:
Core Functionality:
Permission Handling:
Edge Cases:
Platform-Specific:
After completing all TODOs, try these challenges:
Hints for Extensions:
expo-image-manipulator packageexpo-media-library APIallowsMultipleSelection: trueOfficial Documentation:
Permission Best Practices:
Troubleshooting:
// โ WRONG - Crashes if permission denied
const handleTakePhoto = async () => {
const result = await ImagePicker.launchCameraAsync();
// Camera won't open without permission!
};
// โ
CORRECT - Check permission first
const handleTakePhoto = async () => {
const hasPermission = await requestCameraPermission();
if (!hasPermission) return; // Exit if denied
const result = await ImagePicker.launchCameraAsync();
};
// โ WRONG - result.uri doesn't exist!
const handleTakePhoto = async () => {
const result = await ImagePicker.launchCameraAsync();
setSelectedImage(result.uri); // undefined!
};
// โ
CORRECT - Use assets array
const handleTakePhoto = async () => {
const result = await ImagePicker.launchCameraAsync();
if (!result.canceled) {
setSelectedImage(result.assets[0].uri); // Correct path
}
};
// โ WRONG - Crashes if user cancels
const handleTakePhoto = async () => {
const result = await ImagePicker.launchCameraAsync();
setSelectedImage(result.assets[0].uri); // Error if canceled!
};
// โ
CORRECT - Check if user canceled
const handleTakePhoto = async () => {
const result = await ImagePicker.launchCameraAsync();
if (!result.canceled && result.assets) {
setSelectedImage(result.assets[0].uri);
}
};
Your activity is complete when:
โ Permission Handling: Properly requests and checks camera/library permissions with user-friendly messages
โ Image Capture: Successfully opens camera, captures photo, and retrieves image URI
โ UI Integration: Displays selected images with proper sizing, aspect ratio, and clear functionality
Final Check:
# Run this to ensure everything works:
npx expo start --clear
# Test on physical device (camera requires real hardware)
# If camera opens, photo displays, and clear works - you're done! ๐
This activity teaches skills you'll use in your final projects:
Project 1 (Creative Studio): Profile photos using camera/library selection
Project 2 (Learn & Play): Upload lesson images, user avatars
Project 3 (Community Connector): Post images in social feeds
Project 4 (Portfolio App): Project screenshots, profile pictures
Related Activities:
activity-04-camera-snap/
โโโ App.js # Main app with 3 TODOs (65% complete)
โโโ package.json # Dependencies (includes expo-image-picker)
โโโ app.json # Expo config with camera permissions
โโโ babel.config.js # Babel configuration
โโโ .gitignore # Git ignore patterns
โโโ README.md # This file
โโโ components/
โ โโโ ImagePreview.js # โ
100% complete - displays images
โ โโโ PermissionPrompt.js # โ
100% complete - permission UI
โ โโโ ActionButton.js # โ
100% complete - styled buttons
โโโ utils/
โโโ imageHelpers.js # โ
100% complete - optimization utilities
Ready to Build? Start with TODO #1 in App.js and follow the instructions! ๐
Activity 04 | M1: Mobile Foundations | React Native & Expo SDK 54