Practice and reinforce the concepts from Lesson 4
Master instant deployment by:
Time Limit: 5 minutes
Power User Setup:
# Global Expo CLI (if not already installed)
npm install -g @expo/cli
# Login to Expo
expo login
# Verify setup
expo whoami
expo doctor
Account Setup Checklist:
npm install -g @expo/eas-cli
eas login
Time Limit: 5 minutes
# Create new project
npx create-expo-app InstantDeploy --template blank
cd InstantDeploy
# Start development
npx expo start
# Test on device
# Scan QR code with Expo Go app
✅ Checkpoint: App running on your phone via Expo Go!
Create an app worth deploying! Replace App.js
:
import React, { useState, useEffect } from 'react';
import {
StyleSheet,
Text,
View,
TouchableOpacity,
ScrollView,
TextInput,
Alert,
StatusBar,
Platform,
} from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { LinearGradient } from 'expo-linear-gradient';
import * as Haptics from 'expo-haptics';
import * as Updates from 'expo-updates';
export default function App() {
const [notes, setNotes] = useState([]);
const [newNote, setNewNote] = useState('');
const [appVersion, setAppVersion] = useState('1.0.0');
const [updateAvailable, setUpdateAvailable] = useState(false);
useEffect(() => {
loadNotes();
checkForUpdates();
}, []);
const loadNotes = async () => {
try {
const savedNotes = await AsyncStorage.getItem('notes');
if (savedNotes) {
setNotes(JSON.parse(savedNotes));
}
} catch (error) {
console.error('Error loading notes:', error);
}
};
const saveNotes = async (newNotes) => {
try {
await AsyncStorage.setItem('notes', JSON.stringify(newNotes));
} catch (error) {
console.error('Error saving notes:', error);
}
};
const addNote = () => {
if (newNote.trim()) {
const updatedNotes = [...notes, {
id: Date.now(),
text: newNote.trim(),
createdAt: new Date().toLocaleDateString(),
}];
setNotes(updatedNotes);
saveNotes(updatedNotes);
setNewNote('');
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
}
};
const deleteNote = (id) => {
const updatedNotes = notes.filter(note => note.id !== id);
setNotes(updatedNotes);
saveNotes(updatedNotes);
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
};
const checkForUpdates = async () => {
try {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
setUpdateAvailable(true);
}
} catch (error) {
console.error('Error checking for updates:', error);
}
};
const handleUpdate = async () => {
try {
await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
} catch (error) {
Alert.alert('Update Error', 'Failed to update the app');
}
};
return (
<LinearGradient
colors={['#667eea', '#764ba2']}
style={styles.container}
>
<StatusBar barStyle="light-content" />
<View style={styles.header}>
<Text style={styles.title}>Quick Notes</Text>
<Text style={styles.version}>v{appVersion}</Text>
{updateAvailable && (
<TouchableOpacity
style={styles.updateButton}
onPress={handleUpdate}
>
<Text style={styles.updateText}>Update Available</Text>
</TouchableOpacity>
)}
</View>
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
placeholder="Add a new note..."
placeholderTextColor="#999"
value={newNote}
onChangeText={setNewNote}
onSubmitEditing={addNote}
/>
<TouchableOpacity style={styles.addButton} onPress={addNote}>
<Text style={styles.addButtonText}>+</Text>
</TouchableOpacity>
</View>
<ScrollView style={styles.notesContainer}>
{notes.map((note) => (
<View key={note.id} style={styles.noteCard}>
<Text style={styles.noteText}>{note.text}</Text>
<View style={styles.noteFooter}>
<Text style={styles.noteDate}>{note.createdAt}</Text>
<TouchableOpacity
onPress={() => deleteNote(note.id)}
style={styles.deleteButton}
>
<Text style={styles.deleteButtonText}>×</Text>
</TouchableOpacity>
</View>
</View>
))}
</ScrollView>
</LinearGradient>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: Platform.OS === 'ios' ? 50 : 30,
},
header: {
alignItems: 'center',
marginBottom: 20,
paddingHorizontal: 20,
},
title: {
fontSize: 32,
fontWeight: 'bold',
color: 'white',
marginBottom: 5,
},
version: {
fontSize: 16,
color: 'rgba(255,255,255,0.8)',
},
updateButton: {
marginTop: 10,
backgroundColor: 'rgba(255,255,255,0.2)',
paddingHorizontal: 15,
paddingVertical: 5,
borderRadius: 15,
},
updateText: {
color: 'white',
fontSize: 12,
},
inputContainer: {
flexDirection: 'row',
paddingHorizontal: 20,
marginBottom: 20,
},
input: {
flex: 1,
backgroundColor: 'white',
borderRadius: 25,
paddingHorizontal: 20,
paddingVertical: 15,
fontSize: 16,
marginRight: 10,
},
addButton: {
backgroundColor: 'rgba(255,255,255,0.2)',
width: 50,
height: 50,
borderRadius: 25,
alignItems: 'center',
justifyContent: 'center',
},
addButtonText: {
color: 'white',
fontSize: 24,
fontWeight: 'bold',
},
notesContainer: {
flex: 1,
paddingHorizontal: 20,
},
noteCard: {
backgroundColor: 'rgba(255,255,255,0.9)',
borderRadius: 12,
padding: 15,
marginBottom: 10,
},
noteText: {
fontSize: 16,
color: '#333',
marginBottom: 10,
},
noteFooter: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
noteDate: {
fontSize: 12,
color: '#666',
},
deleteButton: {
backgroundColor: '#ff6b6b',
width: 25,
height: 25,
borderRadius: 12.5,
alignItems: 'center',
justifyContent: 'center',
},
deleteButtonText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
},
});
# Install all dependencies
npx expo install @react-native-async-storage/async-storage
npx expo install expo-linear-gradient
npx expo install expo-haptics
npx expo install expo-updates
Update your app.json
for deployment:
{
"expo": {
"name": "Quick Notes",
"slug": "quick-notes-deploy",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#667eea"
},
"assetBundlePatterns": [
"**/*"
],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.yourname.quicknotes"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#667eea"
},
"package": "com.yourname.quicknotes"
},
"web": {
"favicon": "./assets/favicon.png"
},
"extra": {
"eas": {
"projectId": "your-project-id-here"
}
},
"updates": {
"fallbackToCacheTimeout": 0
}
}
}
Time Limit: 5 minutes
# Initialize EAS in your project
eas build:configure
# This creates eas.json with build profiles
Verify your eas.json
:
{
"cli": {
"version": ">= 3.0.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal"
},
"production": {
"distribution": "store"
}
},
"submit": {
"production": {}
}
}
Time Limit: 10 minutes to start builds
Android APK (Internal Distribution):
# Build APK for testing
eas build -p android --profile preview
iOS Simulator Build:
# Build for iOS simulator (if on macOS)
eas build -p ios --profile development
Production Builds:
# Android Play Store
eas build -p android --profile production
# iOS App Store (requires Apple Developer account)
eas build -p ios --profile production
✅ Checkpoint: At least one build started successfully!
Time Limit: 5 minutes
Make a simple change to your app:
// In App.js, change the title
const [appVersion, setAppVersion] = useState('1.0.1'); // Changed from 1.0.0
// Add a new feature or change colors
const styles = StyleSheet.create({
// ... existing styles
title: {
fontSize: 34, // Increased from 32
fontWeight: 'bold',
color: 'white',
marginBottom: 5,
},
});
Publish the update:
# Publish update to all builds
eas update --auto
# Or specify a branch/channel
eas update --branch production --message "Updated title size and version"
Time Limit: 5 minutes
Advanced OTA Testing:
// Add this button to your app to manually check for updates
const checkManualUpdate = async () => {
try {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
Alert.alert(
'Update Available',
'A new version is available. Download now?',
[
{ text: 'Later', style: 'cancel' },
{ text: 'Update', onPress: handleUpdate }
]
);
} else {
Alert.alert('No Updates', 'You have the latest version!');
}
} catch (error) {
Alert.alert('Update Error', 'Could not check for updates');
}
};
One. Development Phase:
# Local development
npx expo start
# Test on device via Expo Go
# Make changes, see them instantly
2. Testing Phase:
# Create preview build
eas build -p android --profile preview
# Share APK with testers
# Install and test on real devices
3. Updates Phase:
# Push OTA updates
eas update --branch preview --message "Bug fixes"
# Test updates work correctly
4. Production Phase:
# Production build
eas build -p android --profile production
eas build -p ios --profile production
# Submit to stores
eas submit -p android
eas submit -p ios
Set up multiple deployment channels:
# Development updates
eas update --branch development
# Staging updates
eas update --branch staging
# Production updates
eas update --branch production
Configure different build profiles for each:
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"channel": "development"
},
"staging": {
"distribution": "internal",
"channel": "staging"
},
"production": {
"distribution": "store",
"channel": "production"
}
}
}
Set up environment-specific configs:
// Create config.js
const config = {
development: {
apiUrl: 'https://dev-api.example.com',
analytics: false,
debugMode: true,
},
staging: {
apiUrl: 'https://staging-api.example.com',
analytics: true,
debugMode: false,
},
production: {
apiUrl: 'https://api.example.com',
analytics: true,
debugMode: false,
}
};
const getConfig = () => {
if (__DEV__) return config.development;
// Use EAS environment variables
const releaseChannel = Updates.releaseChannel;
return config[releaseChannel] || config.production;
};
export default getConfig();
Generate required assets:
# Generate app icons for all sizes
# Use online tools or design software
# Create screenshots for stores
# Use device simulators/emulators
# Prepare store descriptions
# Write compelling app descriptions
Store Metadata Checklist:
Semantic Versioning:
Update Strategy:
Add deployment tracking:
import * as Updates from 'expo-updates';
// Track update events
const trackUpdateEvent = (eventName, properties = {}) => {
// Your analytics service (Firebase, Amplitude, etc.)
console.log(`Update Event: ${eventName}`, properties);
};
useEffect(() => {
const checkAndApplyUpdate = async () => {
try {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
trackUpdateEvent('update_available', {
currentVersion: Updates.manifest?.version,
newVersion: update.manifest?.version,
});
await Updates.fetchUpdateAsync();
trackUpdateEvent('update_downloaded');
// Optionally auto-reload or prompt user
await Updates.reloadAsync();
}
} catch (error) {
trackUpdateEvent('update_error', { error: error.message });
}
};
checkAndApplyUpdate();
}, []);
Successfully Deployed If:
# Create custom build commands
# package.json scripts:
"scripts": {
"build:dev": "eas build -p android --profile development",
"build:staging": "eas build -p android --profile preview",
"build:prod": "eas build --platform all --profile production",
"update:dev": "eas update --branch development",
"update:prod": "eas update --branch production --message"
}
# Add testing before deployment
npm run test
npm run lint
npm run type-check
eas build --profile production
// Implement update rollback
const rollbackUpdate = async () => {
try {
await Updates.reloadAsync();
} catch (error) {
// Handle rollback failure
Alert.alert('Rollback Failed', 'Please restart the app');
}
};
Document your deployment process:
Build Commands Used:
Update Strategy:
Lessons Learned:
Time Investment: 60 minutes total Difficulty Level: Intermediate Prerequisites: React Native basics, Expo account Tools Needed: Expo CLI, EAS CLI, smartphone for testing