By the end of this lesson, you will be able to:
ℹ️ Info Definition: Expo is a platform that simplifies React Native development by providing tools, services, and workflows that eliminate complex native configuration. It allows you to deploy and test apps instantly without traditional build processes.
Traditional mobile development requires:
With Expo, you get:
Traditional Approach | Expo Approach |
---|---|
30+ minute builds | 30-second deployment |
Install multiple IDEs | One command line tool |
Complex device setup | Scan QR code to test |
Weeks for app store approval | Instant updates to users |
💡 Success Story: Companies like TaskRabbit and Flipkart use Expo to deploy updates to millions of users instantly, bypassing lengthy app store reviews!
# Install Expo CLI globally
npm install -g @expo/cli
# Create a new project
npx create-expo-app InstantApp --template typescript
# Navigate to your project
cd InstantApp
# Start the development server
npx expo start
When you run npx expo start
, you get:
› Metro waiting on exp://192.168.1.100:19000
› Scan the QR code above with Expo Go (Android) or the Camera app (iOS)
› Press a │ open Android
› Press i │ open iOS simulator
› Press w │ open web
› Press r │ reload app
› Press m │ toggle menu
// App.tsx - Watch changes happen instantly!
import React, { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Animated } from 'react-native';
export default function InstantDemoApp() {
const [count, setCount] = useState(0);
const [fadeAnim] = useState(new Animated.Value(0));
const [message, setMessage] = useState('Touch the button!');
useEffect(() => {
// Entrance animation
Animated.timing(fadeAnim, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}).start();
}, []);
const handlePress = () => {
const newCount = count + 1;
setCount(newCount);
// Update message based on count
if (newCount === 1) {
setMessage('Great! Keep going!');
} else if (newCount === 5) {
setMessage('You\'re on fire! 🔥');
} else if (newCount === 10) {
setMessage('Amazing! This deployed instantly!');
} else {
setMessage(`Count: ${newCount} - Deployed in real-time!`);
}
// Pulse animation on press
Animated.sequence([
Animated.timing(fadeAnim, {
toValue: 0.5,
duration: 100,
useNativeDriver: true,
}),
Animated.timing(fadeAnim, {
toValue: 1,
duration: 100,
useNativeDriver: true,
}),
]).start();
};
return (
<Animated.View style={[styles.container, { opacity: fadeAnim }]}>
<Text style={styles.title}>⚡ Instant Deployment Demo</Text>
<View style={styles.messageContainer}>
<Text style={styles.message}>{message}</Text>
</View>
<TouchableOpacity style={styles.button} onPress={handlePress}>
<Text style={styles.buttonText}>Press Me! ({count})</Text>
</TouchableOpacity>
<Text style={styles.subtitle}>
Changes appear instantly on your phone! 📱
</Text>
{/* Real-time feature showcase */}
<View style={styles.statusContainer}>
<Text style={styles.statusText}>
✅ Deployed in: ~2 seconds
</Text>
<Text style={styles.statusText}>
✅ No app store needed
</Text>
<Text style={styles.statusText}>
✅ Works on iOS & Android
</Text>
</View>
</Animated.View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#6C63FF',
alignItems: 'center',
justifyContent: 'center',
padding: 20,
},
title: {
fontSize: 28,
fontWeight: 'bold',
color: 'white',
marginBottom: 30,
textAlign: 'center',
},
messageContainer: {
backgroundColor: 'rgba(255, 255, 255, 0.2)',
padding: 20,
borderRadius: 15,
marginBottom: 30,
},
message: {
fontSize: 18,
color: 'white',
textAlign: 'center',
fontWeight: '600',
},
button: {
backgroundColor: '#FF6B6B',
paddingHorizontal: 40,
paddingVertical: 15,
borderRadius: 30,
marginBottom: 30,
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 8,
elevation: 8,
},
buttonText: {
color: 'white',
fontSize: 20,
fontWeight: 'bold',
},
subtitle: {
fontSize: 16,
color: 'white',
opacity: 0.9,
marginBottom: 30,
textAlign: 'center',
},
statusContainer: {
alignItems: 'center',
},
statusText: {
color: 'white',
fontSize: 14,
marginVertical: 2,
opacity: 0.8,
},
});
Try this: Make changes to the code above and watch them appear on your phone instantly!
OTA updates allow you to push changes directly to users' phones without going through app stores:
// Configure automatic updates
import * as Updates from 'expo-updates';
import { useEffect } from 'react';
export const useOTAUpdates = () => {
useEffect(() => {
const checkForUpdates = async () => {
try {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
}
} catch (error) {
console.log('Error checking for updates:', error);
}
};
checkForUpdates();
}, []);
};
// Use in your main app
export default function App() {
useOTAUpdates(); // Automatically check for updates
return (
// Your app content
);
}
# Publish an update to all users instantly
npx expo publish
# Publish to specific release channel
npx expo publish --release-channel staging
# View published updates
npx expo publish:history
EAS Build creates production-ready apps for app stores:
# Install EAS CLI
npm install -g eas-cli
# Configure your project
eas build:configure
# Build for both platforms
eas build --platform all
# Build for specific platform
eas build --platform ios
eas build --platform android
{
"cli": {
"version": ">= 3.0.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal",
"ios": {
"resourceClass": "m1-medium"
}
},
"production": {
"ios": {
"resourceClass": "m1-medium"
}
}
},
"submit": {
"production": {}
}
}
# .github/workflows/deploy.yml
name: Deploy to Expo
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Install dependencies
run: npm ci
- name: Install Expo CLI
run: npm install -g @expo/cli
- name: Publish to Expo
run: npx expo publish
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
AI Prompt:
Create an automated deployment script that:
- Runs tests before deployment
- Optimizes bundle size
- Publishes to different channels based on branch
- Sends deployment notifications
- Handles rollback if issues are detected
// app.config.js - Dynamic configuration
import { ConfigContext, ExpoConfig } from '@expo/config';
export default ({ config }: ConfigContext): ExpoConfig => {
const isProduction = process.env.NODE_ENV === 'production';
return {
...config,
name: isProduction ? 'MyApp' : 'MyApp Dev',
slug: 'my-app',
version: '1.0.0',
extra: {
apiUrl: isProduction
? 'https://api.myapp.com'
: 'https://dev-api.myapp.com',
environment: isProduction ? 'production' : 'development',
},
updates: {
url: 'https://u.expo.dev/your-project-id',
},
runtimeVersion: {
policy: 'sdkVersion',
},
};
};
// utils/config.ts
import Constants from 'expo-constants';
interface Config {
apiUrl: string;
environment: string;
version: string;
}
export const config: Config = {
apiUrl: Constants.expoConfig?.extra?.apiUrl || 'http://localhost:3000',
environment: Constants.expoConfig?.extra?.environment || 'development',
version: Constants.expoConfig?.version || '1.0.0',
};
// Usage in components
import { config } from './utils/config';
export const ApiService = {
baseUrl: config.apiUrl,
async fetchData() {
const response = await fetch(`${this.baseUrl}/data`);
return response.json();
},
};
// Enable development features only in dev
import { __DEV__ } from 'react-native';
import { config } from './utils/config';
export const DevelopmentPanel = () => {
if (!__DEV__ || config.environment === 'production') {
return null;
}
return (
<View style={styles.devPanel}>
<Text>🛠 Development Mode</Text>
<TouchableOpacity onPress={() => console.log('Debug pressed')}>
<Text>Debug Info</Text>
</TouchableOpacity>
</View>
);
};
# Build optimized production bundle
npx expo export --public-url https://your-cdn.com
# Analyze bundle size
npx expo export --dump-assetmap
# Test production build locally
npx expo export
npx serve dist -p 8000
// Setup error monitoring
import * as Sentry from 'sentry-expo';
Sentry.init({
dsn: 'YOUR_SENTRY_DSN',
enableInExpoDevelopment: true,
debug: __DEV__,
});
// Track deployment success
export const trackDeployment = () => {
Sentry.addBreadcrumb({
message: 'App deployed successfully',
level: 'info',
data: {
version: config.version,
environment: config.environment,
},
});
};
In this lesson, you learned:
Code with AI: Try these deployment optimization tasks.
Prompts to try:
Master Expo's deployment tools and you'll ship apps faster than ever before!