Skip to content

Expo Cheatsheet

Expo - The Fastest Way to Build React Native Apps

Expo is an open-source platform for making universal native apps for Android, iOS, and the web with JavaScript and React. It provides a set of tools and services built around React Native and native platforms.

Table of Contents

Installation

Prerequisites

# Install Node.js (version 16 or later)
# Download from nodejs.org

# Verify Node.js installation
node --version
npm --version

# Install Yarn (optional but recommended)
npm install -g yarn

Expo CLI Installation

# Install Expo CLI globally
npm install -g @expo/cli

# Or with Yarn
yarn global add @expo/cli

# Verify installation
expo --version

# Login to Expo account (optional)
expo login

# Check current user
expo whoami

Expo Go App

# Download Expo Go from app stores:
# iOS: App Store - search "Expo Go"
# Android: Google Play Store - search "Expo Go"

# Alternative: Use development build
# For custom native code or third-party libraries

Getting Started

Create New Project

# Create new Expo project
expo init MyApp

# Choose template:
# - blank: minimal app with just the essentials
# - blank (TypeScript): minimal app with TypeScript
# - tabs (TypeScript): several example screens and tabs
# - minimal: bare minimum files

# Navigate to project directory
cd MyApp

# Start development server
expo start

# Alternative: Create with specific template
expo init MyApp --template blank
expo init MyApp --template blank-typescript
expo init MyApp --template tabs

Project Templates

# Blank template
expo init MyApp --template blank

# TypeScript template
expo init MyApp --template blank-typescript

# Navigation template
expo init MyApp --template tabs

# Bare workflow (advanced)
expo init MyApp --template bare-minimum

# Create with npm
npx create-expo-app MyApp

# Create with specific SDK version
expo init MyApp --sdk-version 49.0.0

Project Structure

Basic Structure

MyApp/
├── App.js                 # Main app component
├── app.json              # Expo configuration
├── package.json          # Dependencies and scripts
├── babel.config.js       # Babel configuration
├── assets/               # Images, fonts, etc.
│   ├── icon.png
│   ├── splash.png
│   └── adaptive-icon.png
├── components/           # Reusable components
├── screens/             # Screen components
├── navigation/          # Navigation configuration
├── services/           # API services
├── utils/              # Utility functions
└── constants/          # App constants

App.js Example

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';

export default function App() {
  return (
    <View style={styles.container}>
      <Text>Open up App.js to start working on your app!</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

app.json Configuration

{
  "expo": {
    "name": "MyApp",
    "slug": "myapp",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "userInterfaceStyle": "light",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "assetBundlePatterns": [
      "**/*"
    ],
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "com.yourcompany.myapp"
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/adaptive-icon.png",
        "backgroundColor": "#FFFFFF"
      },
      "package": "com.yourcompany.myapp"
    },
    "web": {
      "favicon": "./assets/favicon.png"
    }
  }
}

Expo CLI

Development Commands

# Start development server
expo start

# Start with specific options
expo start --clear          # Clear cache
expo start --offline        # Work offline
expo start --tunnel         # Use tunnel connection
expo start --lan           # Use LAN connection
expo start --localhost      # Use localhost

# Platform-specific starts
expo start --ios           # Open iOS simulator
expo start --android       # Open Android emulator
expo start --web           # Open web browser

# Install dependencies
expo install package-name

# Install specific version
expo install package-name@version

# Install multiple packages
expo install package1 package2 package3

Project Management

# Check project status
expo doctor

# Upgrade Expo SDK
expo upgrade

# Eject from managed workflow (deprecated)
expo eject

# Prebuild (generate native code)
expo prebuild

# Clean project
expo start --clear

# Run on device
expo start --tunnel

# Generate app icons and splash screens
expo install expo-splash-screen
expo install @expo/vector-icons

Build Commands

# Build for iOS (legacy)
expo build:ios

# Build for Android (legacy)
expo build:android

# Build for web
expo build:web

# Check build status
expo build:status

# Download build
expo build:download

# Modern EAS Build
eas build --platform ios
eas build --platform android
eas build --platform all

Development Workflow

Running on Device

# Method 1: Expo Go app
# 1. Install Expo Go on your device
# 2. Run: expo start
# 3. Scan QR code with Expo Go (Android) or Camera (iOS)

# Method 2: Development build
# 1. Create development build: eas build --profile development
# 2. Install build on device
# 3. Run: expo start --dev-client

# Method 3: Simulator/Emulator
expo start --ios        # iOS Simulator
expo start --android    # Android Emulator

Hot Reloading

// Automatic hot reloading is enabled by default
// Changes to JavaScript files trigger automatic reload

// Disable hot reloading in app.json
{
  "expo": {
    "packagerOpts": {
      "config": "metro.config.js"
    }
  }
}

// Manual reload
// Shake device or press Cmd+R (iOS) / Cmd+M (Android)

Debugging

# Enable remote debugging
# Shake device > Debug Remote JS

# Use Flipper for debugging
npm install --save-dev react-native-flipper

# Debug with VS Code
# Install React Native Tools extension

# Console logging
console.log('Debug message');
console.warn('Warning message');
console.error('Error message');

# React Native Debugger
# Download from GitHub releases
# Start with: expo start
# Open React Native Debugger

Core Components

Basic Components

import React from 'react';
import {
  View,
  Text,
  Image,
  ScrollView,
  TouchableOpacity,
  TextInput,
  StyleSheet
} from 'react-native';

const BasicComponents = () => {
  const [text, setText] = React.useState('');

  return (
    <ScrollView style={styles.container}>
      <View style={styles.section}>
        <Text style={styles.title}>Hello, Expo!</Text>

        <Image
          source={{ uri: 'https://example.com/image.jpg' }}
          style={styles.image}
        />

        <TextInput
          style={styles.input}
          placeholder="Enter text here"
          value={text}
          onChangeText={setText}
        />

        <TouchableOpacity
          style={styles.button}
          onPress={() => alert('Button pressed!')}
        >
          <Text style={styles.buttonText}>Press Me</Text>
        </TouchableOpacity>
      </View>
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
  section: {
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  image: {
    width: 200,
    height: 200,
    marginBottom: 20,
  },
  input: {
    borderWidth: 1,
    borderColor: '#ccc',
    padding: 10,
    marginBottom: 20,
    borderRadius: 5,
  },
  button: {
    backgroundColor: '#007AFF',
    padding: 15,
    borderRadius: 5,
    alignItems: 'center',
  },
  buttonText: {
    color: 'white',
    fontWeight: 'bold',
  },
});

export default BasicComponents;

Lists and Data

import React from 'react';
import {
  FlatList,
  SectionList,
  Text,
  View,
  StyleSheet
} from 'react-native';

const ListExamples = () => {
  const data = [
    { id: '1', title: 'Item 1' },
    { id: '2', title: 'Item 2' },
    { id: '3', title: 'Item 3' },
  ];

  const sectionData = [
    {
      title: 'Section 1',
      data: ['Item 1', 'Item 2'],
    },
    {
      title: 'Section 2',
      data: ['Item 3', 'Item 4'],
    },
  ];

  const renderItem = ({ item }) => (
    <View style={styles.item}>
      <Text>{item.title}</Text>
    </View>
  );

  const renderSectionItem = ({ item }) => (
    <View style={styles.item}>
      <Text>{item}</Text>
    </View>
  );

  const renderSectionHeader = ({ section: { title } }) => (
    <View style={styles.header}>
      <Text style={styles.headerText}>{title}</Text>
    </View>
  );

  return (
    <View style={styles.container}>
      <Text style={styles.title}>FlatList Example</Text>
      <FlatList
        data={data}
        renderItem={renderItem}
        keyExtractor={item => item.id}
        style={styles.list}
      />

      <Text style={styles.title}>SectionList Example</Text>
      <SectionList
        sections={sectionData}
        renderItem={renderSectionItem}
        renderSectionHeader={renderSectionHeader}
        keyExtractor={(item, index) => item + index}
        style={styles.list}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  title: {
    fontSize: 18,
    fontWeight: 'bold',
    marginVertical: 10,
  },
  list: {
    maxHeight: 200,
  },
  item: {
    padding: 10,
    borderBottomWidth: 1,
    borderBottomColor: '#ccc',
  },
  header: {
    backgroundColor: '#f0f0f0',
    padding: 10,
  },
  headerText: {
    fontWeight: 'bold',
  },
});

export default ListExamples;

React Navigation Setup

# Install React Navigation
npm install @react-navigation/native

# Install dependencies for Expo
expo install react-native-screens react-native-safe-area-context

# Install navigators
npm install @react-navigation/stack
npm install @react-navigation/bottom-tabs
npm install @react-navigation/drawer

Stack Navigator

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { View, Text, Button } from 'react-native';

const Stack = createStackNavigator();

const HomeScreen = ({ navigation }) => (
  <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
    <Text>Home Screen</Text>
    <Button
      title="Go to Details"
      onPress={() => navigation.navigate('Details', { itemId: 86 })}
    />
  </View>
);

const DetailsScreen = ({ route, navigation }) => {
  const { itemId } = route.params;

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Details Screen</Text>
      <Text>Item ID: {itemId}</Text>
      <Button
        title="Go back"
        onPress={() => navigation.goBack()}
      />
    </View>
  );
};

const App = () => {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen 
          name="Home" 
          component={HomeScreen}
          options={{ title: 'My Home' }}
        />
        <Stack.Screen 
          name="Details" 
          component={DetailsScreen}
          options={({ route }) => ({ title: `Item ${route.params.itemId}` })}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
};

export default App;

Tab Navigator

import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons';

const Tab = createBottomTabNavigator();

const HomeScreen = () => (
  <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
    <Text>Home!</Text>
  </View>
);

const SettingsScreen = () => (
  <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
    <Text>Settings!</Text>
  </View>
);

const TabNavigator = () => {
  return (
    <Tab.Navigator
      screenOptions={({ route }) => ({
        tabBarIcon: ({ focused, color, size }) => {
          let iconName;

          if (route.name === 'Home') {
            iconName = focused ? 'home' : 'home-outline';
          } else if (route.name === 'Settings') {
            iconName = focused ? 'settings' : 'settings-outline';
          }

          return <Ionicons name={iconName} size={size} color={color} />;
        },
        tabBarActiveTintColor: 'tomato',
        tabBarInactiveTintColor: 'gray',
      })}
    >
      <Tab.Screen name="Home" component={HomeScreen} />
      <Tab.Screen name="Settings" component={SettingsScreen} />
    </Tab.Navigator>
  );
};

export default TabNavigator;

State Management

React Hooks

import React, { useState, useEffect, useContext, useReducer } from 'react';

// useState Hook
const CounterComponent = () => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');

  return (
    <View>
      <Text>Count: {count}</Text>
      <Button title="+" onPress={() => setCount(count + 1)} />
      <Button title="-" onPress={() => setCount(count - 1)} />

      <TextInput
        value={name}
        onChangeText={setName}
        placeholder="Enter name"
      />
    </View>
  );
};

// useEffect Hook
const DataComponent = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchData();
  }, []);

  const fetchData = async () => {
    try {
      const response = await fetch('https://api.example.com/data');
      const result = await response.json();
      setData(result);
    } catch (error) {
      console.error('Error fetching data:', error);
    } finally {
      setLoading(false);
    }
  };

  if (loading) {
    return <Text>Loading...</Text>;
  }

  return (
    <View>
      <Text>{JSON.stringify(data)}</Text>
    </View>
  );
};

// useContext Hook
const ThemeContext = React.createContext();

const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(theme === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

const ThemedComponent = () => {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <View style={{ backgroundColor: theme === 'light' ? '#fff' : '#333' }}>
      <Text style={{ color: theme === 'light' ? '#000' : '#fff' }}>
        Current theme: {theme}
      </Text>
      <Button title="Toggle Theme" onPress={toggleTheme} />
    </View>
  );
};

Redux Setup

# Install Redux
npm install @reduxjs/toolkit react-redux

# Install Redux DevTools (optional)
npm install redux-devtools-extension
// store.js
import { configureStore, createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0,
  },
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;

export const store = configureStore({
  reducer: {
    counter: counterSlice.reducer,
  },
});

// App.js
import React from 'react';
import { Provider } from 'react-redux';
import { store } from './store';
import Counter from './Counter';

const App = () => {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
};

// Counter.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from './store';

const Counter = () => {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();

  return (
    <View>
      <Text>Count: {count}</Text>
      <Button title="+" onPress={() => dispatch(increment())} />
      <Button title="-" onPress={() => dispatch(decrement())} />
      <Button title="+5" onPress={() => dispatch(incrementByAmount(5))} />
    </View>
  );
};

Styling

StyleSheet

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const StyledComponent = () => {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Styled Text</Text>
      <View style={styles.box}>
        <Text style={styles.boxText}>Box Content</Text>
      </View>
      <View style={[styles.box, styles.redBox]}>
        <Text style={styles.boxText}>Red Box</Text>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#333',
    marginBottom: 20,
    textAlign: 'center',
  },
  box: {
    backgroundColor: '#007AFF',
    padding: 20,
    marginVertical: 10,
    borderRadius: 10,
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
    elevation: 5,
  },
  redBox: {
    backgroundColor: '#FF3B30',
  },
  boxText: {
    color: 'white',
    fontSize: 16,
    textAlign: 'center',
  },
});

export default StyledComponent;

Responsive Design

import React from 'react';
import { View, Text, Dimensions, StyleSheet } from 'react-native';

const { width, height } = Dimensions.get('window');

const ResponsiveComponent = () => {
  return (
    <View style={styles.container}>
      <View style={styles.responsiveBox}>
        <Text>Responsive Box</Text>
      </View>
      <View style={styles.flexContainer}>
        <View style={styles.flexItem}>
          <Text>Item 1</Text>
        </View>
        <View style={styles.flexItem}>
          <Text>Item 2</Text>
        </View>
        <View style={styles.flexItem}>
          <Text>Item 3</Text>
        </View>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  responsiveBox: {
    width: width * 0.8,
    height: height * 0.2,
    backgroundColor: '#007AFF',
    justifyContent: 'center',
    alignItems: 'center',
    marginBottom: 20,
  },
  flexContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  flexItem: {
    flex: 1,
    backgroundColor: '#34C759',
    margin: 5,
    padding: 20,
    alignItems: 'center',
  },
});

export default ResponsiveComponent;

Styled Components (Alternative)

# Install styled-components
npm install styled-components
import React from 'react';
import styled from 'styled-components/native';

const Container = styled.View`
  flex: 1;
  padding: 20px;
  background-color: #f5f5f5;
`;

const Title = styled.Text`
  font-size: 24px;
  font-weight: bold;
  color: #333;
  margin-bottom: 20px;
  text-align: center;
`;

const Button = styled.TouchableOpacity`
  background-color: ${props => props.primary ? '#007AFF' : '#34C759'};
  padding: 15px;
  border-radius: 10px;
  margin-vertical: 10px;
`;

const ButtonText = styled.Text`
  color: white;
  text-align: center;
  font-weight: bold;
`;

const StyledComponentExample = () => {
  return (
    <Container>
      <Title>Styled Components</Title>
      <Button primary onPress={() => alert('Primary button')}>
        <ButtonText>Primary Button</ButtonText>
      </Button>
      <Button onPress={() => alert('Secondary button')}>
        <ButtonText>Secondary Button</ButtonText>
      </Button>
    </Container>
  );
};

export default StyledComponentExample;

APIs and Services

Expo APIs

import * as Device from 'expo-device';
import * as Battery from 'expo-battery';
import * as Network from 'expo-network';
import * as Application from 'expo-application';

const DeviceInfo = () => {
  const [deviceInfo, setDeviceInfo] = useState({});

  useEffect(() => {
    getDeviceInfo();
  }, []);

  const getDeviceInfo = async () => {
    const info = {
      deviceName: Device.deviceName,
      deviceType: Device.deviceType,
      osName: Device.osName,
      osVersion: Device.osVersion,
      batteryLevel: await Battery.getBatteryLevelAsync(),
      networkState: await Network.getNetworkStateAsync(),
      appVersion: Application.nativeApplicationVersion,
    };
    setDeviceInfo(info);
  };

  return (
    <View>
      <Text>Device: {deviceInfo.deviceName}</Text>
      <Text>OS: {deviceInfo.osName} {deviceInfo.osVersion}</Text>
      <Text>Battery: {Math.round(deviceInfo.batteryLevel * 100)}%</Text>
      <Text>Network: {deviceInfo.networkState?.type}</Text>
      <Text>App Version: {deviceInfo.appVersion}</Text>
    </View>
  );
};

HTTP Requests

import React, { useState, useEffect } from 'react';

// Using fetch
const FetchExample = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchData();
  }, []);

  const fetchData = async () => {
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/posts');
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      const result = await response.json();
      setData(result);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  const postData = async (newPost) => {
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(newPost),
      });
      const result = await response.json();
      console.log('Posted:', result);
    } catch (err) {
      console.error('Error posting data:', err);
    }
  };

  if (loading) return <Text>Loading...</Text>;
  if (error) return <Text>Error: {error}</Text>;

  return (
    <FlatList
      data={data}
      keyExtractor={item => item.id.toString()}
      renderItem={({ item }) => (
        <View style={{ padding: 10 }}>
          <Text style={{ fontWeight: 'bold' }}>{item.title}</Text>
          <Text>{item.body}</Text>
        </View>
      )}
    />
  );
};

// Using Axios (alternative)
// npm install axios
import axios from 'axios';

const AxiosExample = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetchWithAxios();
  }, []);

  const fetchWithAxios = async () => {
    try {
      const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
      setData(response.data);
    } catch (error) {
      console.error('Axios error:', error);
    }
  };

  const postWithAxios = async (newPost) => {
    try {
      const response = await axios.post('https://jsonplaceholder.typicode.com/posts', newPost);
      console.log('Posted with Axios:', response.data);
    } catch (error) {
      console.error('Axios post error:', error);
    }
  };

  return (
    <View>
      {/* Render data */}
    </View>
  );
};

Push Notifications

Setup

# Install Expo Notifications
expo install expo-notifications

# For bare workflow, additional setup required
# See: https://docs.expo.dev/push-notifications/overview/

Basic Notifications

import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import { Platform } from 'react-native';

// Configure notifications
Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: false,
    shouldSetBadge: false,
  }),
});

const NotificationExample = () => {
  const [expoPushToken, setExpoPushToken] = useState('');
  const [notification, setNotification] = useState(false);

  useEffect(() => {
    registerForPushNotificationsAsync().then(token => setExpoPushToken(token));

    const notificationListener = Notifications.addNotificationReceivedListener(notification => {
      setNotification(notification);
    });

    const responseListener = Notifications.addNotificationResponseReceivedListener(response => {
      console.log(response);
    });

    return () => {
      Notifications.removeNotificationSubscription(notificationListener);
      Notifications.removeNotificationSubscription(responseListener);
    };
  }, []);

  const schedulePushNotification = async () => {
    await Notifications.scheduleNotificationAsync({
      content: {
        title: "You've got mail! 📬",
        body: 'Here is the notification body',
        data: { data: 'goes here' },
      },
      trigger: { seconds: 2 },
    });
  };

  const sendPushNotification = async (expoPushToken) => {
    const message = {
      to: expoPushToken,
      sound: 'default',
      title: 'Original Title',
      body: 'And here is the body!',
      data: { someData: 'goes here' },
    };

    await fetch('https://exp.host/--/api/v2/push/send', {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        'Accept-encoding': 'gzip, deflate',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(message),
    });
  };

  return (
    <View>
      <Text>Your expo push token: {expoPushToken}</Text>
      <Button
        title="Press to schedule a notification"
        onPress={schedulePushNotification}
      />
      <Button
        title="Press to send a push notification"
        onPress={() => sendPushNotification(expoPushToken)}
      />
    </View>
  );
};

async function registerForPushNotificationsAsync() {
  let token;
  if (Device.isDevice) {
    const { status: existingStatus } = await Notifications.getPermissionsAsync();
    let finalStatus = existingStatus;
    if (existingStatus !== 'granted') {
      const { status } = await Notifications.requestPermissionsAsync();
      finalStatus = status;
    }
    if (finalStatus !== 'granted') {
      alert('Failed to get push token for push notification!');
      return;
    }
    token = (await Notifications.getExpoPushTokenAsync()).data;
  } else {
    alert('Must use physical device for Push Notifications');
  }

  if (Platform.OS === 'android') {
    Notifications.setNotificationChannelAsync('default', {
      name: 'default',
      importance: Notifications.AndroidImportance.MAX,
      vibrationPattern: [0, 250, 250, 250],
      lightColor: '#FF231F7C',
    });
  }

  return token;
}

File System

File Operations

# Install FileSystem
expo install expo-file-system
import * as FileSystem from 'expo-file-system';

const FileSystemExample = () => {
  const [fileContent, setFileContent] = useState('');

  const writeFile = async () => {
    const fileUri = FileSystem.documentDirectory + 'test.txt';
    const content = 'Hello, Expo FileSystem!';

    try {
      await FileSystem.writeAsStringAsync(fileUri, content);
      alert('File written successfully!');
    } catch (error) {
      console.error('Error writing file:', error);
    }
  };

  const readFile = async () => {
    const fileUri = FileSystem.documentDirectory + 'test.txt';

    try {
      const content = await FileSystem.readAsStringAsync(fileUri);
      setFileContent(content);
    } catch (error) {
      console.error('Error reading file:', error);
    }
  };

  const deleteFile = async () => {
    const fileUri = FileSystem.documentDirectory + 'test.txt';

    try {
      await FileSystem.deleteAsync(fileUri);
      alert('File deleted successfully!');
      setFileContent('');
    } catch (error) {
      console.error('Error deleting file:', error);
    }
  };

  const getFileInfo = async () => {
    const fileUri = FileSystem.documentDirectory + 'test.txt';

    try {
      const info = await FileSystem.getInfoAsync(fileUri);
      console.log('File info:', info);
    } catch (error) {
      console.error('Error getting file info:', error);
    }
  };

  const downloadFile = async () => {
    const fileUri = FileSystem.documentDirectory + 'downloaded.jpg';
    const downloadUrl = 'https://example.com/image.jpg';

    try {
      const { uri } = await FileSystem.downloadAsync(downloadUrl, fileUri);
      console.log('Downloaded to:', uri);
    } catch (error) {
      console.error('Error downloading file:', error);
    }
  };

  return (
    <View style={{ padding: 20 }}>
      <Button title="Write File" onPress={writeFile} />
      <Button title="Read File" onPress={readFile} />
      <Button title="Delete File" onPress={deleteFile} />
      <Button title="Get File Info" onPress={getFileInfo} />
      <Button title="Download File" onPress={downloadFile} />

      {fileContent ? (
        <View style={{ marginTop: 20 }}>
          <Text>File Content:</Text>
          <Text>{fileContent}</Text>
        </View>
      ) : null}
    </View>
  );
};

Camera and Media

Camera

# Install Camera
expo install expo-camera
import React, { useState, useEffect, useRef } from 'react';
import { Camera } from 'expo-camera';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';

const CameraExample = () => {
  const [hasPermission, setHasPermission] = useState(null);
  const [type, setType] = useState(Camera.Constants.Type.back);
  const cameraRef = useRef(null);

  useEffect(() => {
    (async () => {
      const { status } = await Camera.requestCameraPermissionsAsync();
      setHasPermission(status === 'granted');
    })();
  }, []);

  const takePicture = async () => {
    if (cameraRef.current) {
      const photo = await cameraRef.current.takePictureAsync();
      console.log('Photo taken:', photo.uri);
      // Save or process the photo
    }
  };

  const recordVideo = async () => {
    if (cameraRef.current) {
      const video = await cameraRef.current.recordAsync();
      console.log('Video recorded:', video.uri);
    }
  };

  const stopRecording = () => {
    if (cameraRef.current) {
      cameraRef.current.stopRecording();
    }
  };

  if (hasPermission === null) {
    return <View />;
  }
  if (hasPermission === false) {
    return <Text>No access to camera</Text>;
  }

  return (
    <View style={styles.container}>
      <Camera style={styles.camera} type={type} ref={cameraRef}>
        <View style={styles.buttonContainer}>
          <TouchableOpacity
            style={styles.button}
            onPress={() => {
              setType(
                type === Camera.Constants.Type.back
                  ? Camera.Constants.Type.front
                  : Camera.Constants.Type.back
              );
            }}
          >
            <Text style={styles.text}>Flip</Text>
          </TouchableOpacity>

          <TouchableOpacity style={styles.button} onPress={takePicture}>
            <Text style={styles.text}>Take Photo</Text>
          </TouchableOpacity>

          <TouchableOpacity style={styles.button} onPress={recordVideo}>
            <Text style={styles.text}>Record</Text>
          </TouchableOpacity>

          <TouchableOpacity style={styles.button} onPress={stopRecording}>
            <Text style={styles.text}>Stop</Text>
          </TouchableOpacity>
        </View>
      </Camera>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  camera: {
    flex: 1,
  },
  buttonContainer: {
    flex: 1,
    backgroundColor: 'transparent',
    flexDirection: 'row',
    margin: 20,
  },
  button: {
    flex: 0.1,
    alignSelf: 'flex-end',
    alignItems: 'center',
    backgroundColor: 'rgba(0,0,0,0.5)',
    padding: 10,
    margin: 5,
  },
  text: {
    fontSize: 18,
    color: 'white',
  },
});

export default CameraExample;

Image Picker

# Install Image Picker
expo install expo-image-picker
import * as ImagePicker from 'expo-image-picker';
import { Image } from 'react-native';

const ImagePickerExample = () => {
  const [image, setImage] = useState(null);

  const pickImage = async () => {
    // Request permission
    const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
    if (status !== 'granted') {
      alert('Sorry, we need camera roll permissions to make this work!');
      return;
    }

    // Pick image
    const result = await ImagePicker.launchImageLibraryAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.All,
      allowsEditing: true,
      aspect: [4, 3],
      quality: 1,
    });

    if (!result.cancelled) {
      setImage(result.uri);
    }
  };

  const takePhoto = async () => {
    // Request permission
    const { status } = await ImagePicker.requestCameraPermissionsAsync();
    if (status !== 'granted') {
      alert('Sorry, we need camera permissions to make this work!');
      return;
    }

    // Take photo
    const result = await ImagePicker.launchCameraAsync({
      allowsEditing: true,
      aspect: [4, 3],
      quality: 1,
    });

    if (!result.cancelled) {
      setImage(result.uri);
    }
  };

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Button title="Pick an image from camera roll" onPress={pickImage} />
      <Button title="Take a photo" onPress={takePhoto} />
      {image && <Image source={{ uri: image }} style={{ width: 200, height: 200 }} />}
    </View>
  );
};

Location Services

Location Setup

# Install Location
expo install expo-location
import * as Location from 'expo-location';

const LocationExample = () => {
  const [location, setLocation] = useState(null);
  const [errorMsg, setErrorMsg] = useState(null);

  useEffect(() => {
    getCurrentLocation();
  }, []);

  const getCurrentLocation = async () => {
    let { status } = await Location.requestForegroundPermissionsAsync();
    if (status !== 'granted') {
      setErrorMsg('Permission to access location was denied');
      return;
    }

    let location = await Location.getCurrentPositionAsync({});
    setLocation(location);
  };

  const watchLocation = async () => {
    let { status } = await Location.requestForegroundPermissionsAsync();
    if (status !== 'granted') {
      setErrorMsg('Permission to access location was denied');
      return;
    }

    const subscription = await Location.watchPositionAsync(
      {
        accuracy: Location.Accuracy.High,
        timeInterval: 1000,
        distanceInterval: 1,
      },
      (location) => {
        setLocation(location);
      }
    );

    // Don't forget to unsubscribe when component unmounts
    return () => subscription.remove();
  };

  const reverseGeocode = async () => {
    if (location) {
      const { latitude, longitude } = location.coords;
      const address = await Location.reverseGeocodeAsync({
        latitude,
        longitude,
      });
      console.log('Address:', address);
    }
  };

  const geocode = async (address) => {
    const coords = await Location.geocodeAsync(address);
    console.log('Coordinates:', coords);
  };

  let text = 'Waiting..';
  if (errorMsg) {
    text = errorMsg;
  } else if (location) {
    text = JSON.stringify(location);
  }

  return (
    <View style={{ padding: 20 }}>
      <Text>{text}</Text>
      <Button title="Get Current Location" onPress={getCurrentLocation} />
      <Button title="Watch Location" onPress={watchLocation} />
      <Button title="Reverse Geocode" onPress={reverseGeocode} />
      <Button 
        title="Geocode Address" 
        onPress={() => geocode('1600 Amphitheatre Parkway, Mountain View, CA')} 
      />
    </View>
  );
};

Authentication

Expo AuthSession

# Install AuthSession
expo install expo-auth-session expo-crypto
import * as AuthSession from 'expo-auth-session';
import * as WebBrowser from 'expo-web-browser';

WebBrowser.maybeCompleteAuthSession();

const AuthExample = () => {
  const [user, setUser] = useState(null);

  // Google OAuth
  const [request, response, promptAsync] = AuthSession.useAuthRequest(
    {
      clientId: 'YOUR_GOOGLE_CLIENT_ID',
      scopes: ['openid', 'profile', 'email'],
      redirectUri: AuthSession.makeRedirectUri({
        useProxy: true,
      }),
    },
    {
      authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
    }
  );

  useEffect(() => {
    if (response?.type === 'success') {
      const { authentication } = response;
      // Use the access token to get user info
      fetchUserInfo(authentication.accessToken);
    }
  }, [response]);

  const fetchUserInfo = async (token) => {
    try {
      const response = await fetch('https://www.googleapis.com/userinfo/v2/me', {
        headers: { Authorization: `Bearer ${token}` },
      });
      const userInfo = await response.json();
      setUser(userInfo);
    } catch (error) {
      console.error('Error fetching user info:', error);
    }
  };

  const signIn = () => {
    promptAsync();
  };

  const signOut = () => {
    setUser(null);
  };

  return (
    <View style={{ padding: 20 }}>
      {user ? (
        <View>
          <Text>Welcome, {user.name}!</Text>
          <Text>Email: {user.email}</Text>
          <Button title="Sign Out" onPress={signOut} />
        </View>
      ) : (
        <Button
          disabled={!request}
          title="Sign in with Google"
          onPress={signIn}
        />
      )}
    </View>
  );
};

Firebase Authentication

# Install Firebase
npm install firebase
import { initializeApp } from 'firebase/app';
import { 
  getAuth, 
  signInWithEmailAndPassword, 
  createUserWithEmailAndPassword,
  signOut,
  onAuthStateChanged 
} from 'firebase/auth';

const firebaseConfig = {
  // Your Firebase config
};

const app = initializeApp(firebaseConfig);
const auth = getAuth(app);

const FirebaseAuthExample = () => {
  const [user, setUser] = useState(null);
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      setUser(user);
    });

    return unsubscribe;
  }, []);

  const signUp = async () => {
    try {
      await createUserWithEmailAndPassword(auth, email, password);
    } catch (error) {
      alert(error.message);
    }
  };

  const signIn = async () => {
    try {
      await signInWithEmailAndPassword(auth, email, password);
    } catch (error) {
      alert(error.message);
    }
  };

  const handleSignOut = async () => {
    try {
      await signOut(auth);
    } catch (error) {
      alert(error.message);
    }
  };

  return (
    <View style={{ padding: 20 }}>
      {user ? (
        <View>
          <Text>Welcome, {user.email}!</Text>
          <Button title="Sign Out" onPress={handleSignOut} />
        </View>
      ) : (
        <View>
          <TextInput
            placeholder="Email"
            value={email}
            onChangeText={setEmail}
            style={{ borderWidth: 1, padding: 10, marginBottom: 10 }}
          />
          <TextInput
            placeholder="Password"
            value={password}
            onChangeText={setPassword}
            secureTextEntry
            style={{ borderWidth: 1, padding: 10, marginBottom: 10 }}
          />
          <Button title="Sign In" onPress={signIn} />
          <Button title="Sign Up" onPress={signUp} />
        </View>
      )}
    </View>
  );
};

Building and Publishing

Expo Build (Legacy)

# Build for iOS
expo build:ios

# Build for Android
expo build:android

# Build for web
expo build:web

# Check build status
expo build:status

# Download build
expo build:download

# Publish to Expo
expo publish

# Set release channel
expo publish --release-channel production

App Store Deployment

# iOS App Store
# 1. Build IPA: expo build:ios
# 2. Download IPA
# 3. Upload to App Store Connect using Transporter or Xcode

# Google Play Store
# 1. Build APK/AAB: expo build:android
# 2. Download APK/AAB
# 3. Upload to Google Play Console

EAS (Expo Application Services)

EAS Setup

# Install EAS CLI
npm install -g eas-cli

# Login to Expo account
eas login

# Initialize EAS in project
eas build:configure

# Create development build
eas build --profile development --platform ios
eas build --profile development --platform android

# Create production build
eas build --profile production --platform ios
eas build --profile production --platform android

# Build for all platforms
eas build --platform all

EAS Configuration

// eas.json
{
  "cli": {
    "version": ">= 2.0.0"
  },
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal",
      "ios": {
        "resourceClass": "m1-medium"
      }
    },
    "preview": {
      "distribution": "internal"
    },
    "production": {
      "ios": {
        "resourceClass": "m1-medium"
      }
    }
  },
  "submit": {
    "production": {
      "ios": {
        "appleId": "your-apple-id@example.com",
        "ascAppId": "1234567890",
        "appleTeamId": "ABCDEF1234"
      },
      "android": {
        "serviceAccountKeyPath": "../path/to/api-key.json",
        "track": "internal"
      }
    }
  }
}

EAS Submit

# Submit to app stores
eas submit --platform ios
eas submit --platform android

# Submit specific build
eas submit --platform ios --id your-build-id

# Submit with custom configuration
eas submit --platform android --profile production

EAS Update

# Install EAS Update
expo install expo-updates

# Configure EAS Update
eas update:configure

# Publish update
eas update --branch main --message "Bug fixes"

# Publish to specific channel
eas update --channel production --message "Production update"

# View updates
eas update:list

# Delete update
eas update:delete --group-id your-group-id

Best Practices

Project Structure

src/
├── components/           # Reusable components
│   ├── common/          # Common UI components
│   ├── forms/           # Form components
│   └── navigation/      # Navigation components
├── screens/             # Screen components
│   ├── auth/           # Authentication screens
│   ├── main/           # Main app screens
│   └── settings/       # Settings screens
├── services/           # API services
│   ├── api.js          # API configuration
│   ├── auth.js         # Authentication service
│   └── storage.js      # Storage service
├── utils/              # Utility functions
│   ├── constants.js    # App constants
│   ├── helpers.js      # Helper functions
│   └── validation.js   # Validation functions
├── hooks/              # Custom hooks
├── context/            # React context
└── assets/             # Static assets
    ├── images/
    ├── fonts/
    └── icons/

Performance Optimization

// Use React.memo for expensive components
const ExpensiveComponent = React.memo(({ data }) => {
  return (
    <View>
      {/* Expensive rendering */}
    </View>
  );
});

// Use useMemo for expensive calculations
const ExpensiveCalculation = ({ items }) => {
  const expensiveValue = useMemo(() => {
    return items.reduce((acc, item) => acc + item.value, 0);
  }, [items]);

  return <Text>{expensiveValue}</Text>;
};

// Use useCallback for event handlers
const ListComponent = ({ items, onItemPress }) => {
  const handlePress = useCallback((item) => {
    onItemPress(item);
  }, [onItemPress]);

  return (
    <FlatList
      data={items}
      renderItem={({ item }) => (
        <TouchableOpacity onPress={() => handlePress(item)}>
          <Text>{item.title}</Text>
        </TouchableOpacity>
      )}
      keyExtractor={item => item.id}
      getItemLayout={(data, index) => ({
        length: ITEM_HEIGHT,
        offset: ITEM_HEIGHT * index,
        index,
      })}
    />
  );
};

// Optimize images
const OptimizedImage = ({ source, style }) => {
  return (
    <Image
      source={source}
      style={style}
      resizeMode="cover"
      fadeDuration={0}
    />
  );
};

Error Handling

// Error Boundary
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Error caught by boundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
          <Text>Something went wrong.</Text>
          <Button
            title="Try again"
            onPress={() => this.setState({ hasError: false })}
          />
        </View>
      );
    }

    return this.props.children;
  }
}

// Global error handler
const setGlobalErrorHandler = () => {
  const defaultHandler = ErrorUtils.getGlobalHandler();

  ErrorUtils.setGlobalHandler((error, isFatal) => {
    console.error('Global error:', error);
    // Log to crash reporting service
    defaultHandler(error, isFatal);
  });
};

// Async error handling
const safeAsyncFunction = async () => {
  try {
    const result = await riskyAsyncOperation();
    return result;
  } catch (error) {
    console.error('Async error:', error);
    // Handle error appropriately
    throw error;
  }
};

Security Best Practices

// Secure storage
import * as SecureStore from 'expo-secure-store';

const storeSecureData = async (key, value) => {
  await SecureStore.setItemAsync(key, value);
};

const getSecureData = async (key) => {
  return await SecureStore.getItemAsync(key);
};

// API key protection
// Never store API keys in code
// Use environment variables or secure storage

// Input validation
const validateEmail = (email) => {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
};

const sanitizeInput = (input) => {
  return input.trim().replace(/[<>]/g, '');
};

// Network security
const secureApiCall = async (url, options = {}) => {
  const defaultOptions = {
    headers: {
      'Content-Type': 'application/json',
      // Add authentication headers
    },
    ...options,
  };

  try {
    const response = await fetch(url, defaultOptions);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    console.error('API call failed:', error);
    throw error;
  }
};

Summary

Expo is a powerful platform that simplifies React Native development by providing:

  • Rapid Development: Quick project setup and development workflow
  • Rich API Access: Comprehensive set of native APIs without writing native code
  • Universal Apps: Build for iOS, Android, and web from a single codebase
  • Over-the-Air Updates: Push updates without app store approval
  • Development Tools: Excellent debugging and testing tools
  • Build Services: Cloud-based building and deployment with EAS
  • Community: Large ecosystem and active community support

Expo is ideal for rapid prototyping, MVPs, and production apps that don't require extensive native customization. For apps needing custom native modules, Expo provides a bare workflow and development builds to maintain flexibility while keeping the developer experience smooth.