Expo

Mobile DevelopmentReact NativeCross-platformJavaScriptTypeScriptDevelopment Tools

Mobile Platform

Expo

Overview

Expo is a revolutionary platform that dramatically improves the React Native development experience. It significantly simplifies build, test, and deployment processes, accelerating cross-platform app development. As of 2025, Expo has established itself as the de facto standard toolchain for React Native development, with EAS (Expo Application Services) making CI/CD pipeline construction dramatically easier.

Details

Expo serves as the core platform in the React Native ecosystem, focusing on improving developer productivity. It minimizes the complex environment setup traditionally required for React Native development and the preparation of iOS/Android-specific development environments, enabling full-scale native app development with just JavaScript and TypeScript knowledge.

The 2025 updates provide an especially friendly workflow for web developers, with the Expo Go app offering instant preview functionality that allows you to see code changes in real-time on physical devices. Additionally, the EAS Build service enables cloud-based app building and distribution, realizing professional app development workflows regardless of development team size.

Advantages and Disadvantages

Advantages

  • Dramatic improvement in development efficiency: Expo CLI enables completion from project setup to production deployment in minutes
  • Cross-platform support: Develop iOS, Android, and web apps from a single codebase
  • Rich API library: Unified access to 50+ native features including camera, GPS, push notifications
  • Hot reload: Immediate reflection of code changes, dramatically shortening development cycles
  • OTA (Over The Air) updates: Deploy JavaScript updates without app store review
  • EAS integration: Complete integration with cloud-based build, test, and distribution services

Disadvantages

  • React Native dependency: May be affected by React Native limitations and issues
  • Custom native code: Additional setup required when special native functionality is needed
  • App size: Expo SDK may slightly increase app size in some cases
  • Platform dependency: High dependency on the Expo ecosystem

Reference Pages

Code Examples

Project Setup and Initialization

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

# Create new project
npx create-expo-app MyMobileApp

# Navigate to project directory
cd MyMobileApp

# Start development server
npx expo start

Authentication Implementation (Firebase Auth Integration)

// App.tsx
import { initializeApp } from 'firebase/app';
import { getAuth, signInWithEmailAndPassword, createUserWithEmailAndPassword } from 'firebase/auth';
import { useState } from 'react';
import { View, TextInput, Button, Alert } from 'react-native';

const firebaseConfig = {
  // Firebase configuration
};

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

export default function AuthScreen() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const signIn = async () => {
    try {
      await signInWithEmailAndPassword(auth, email, password);
      Alert.alert('Success', 'Signed in successfully');
    } catch (error) {
      Alert.alert('Error', error.message);
    }
  };

  const signUp = async () => {
    try {
      await createUserWithEmailAndPassword(auth, email, password);
      Alert.alert('Success', 'Account created successfully');
    } catch (error) {
      Alert.alert('Error', error.message);
    }
  };

  return (
    <View style={{ padding: 20 }}>
      <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>
  );
}

Backend Integration (API Communication)

// services/api.ts
const API_BASE_URL = 'https://your-api.com/api';

interface User {
  id: string;
  name: string;
  email: string;
}

class ApiService {
  private async request(endpoint: string, options: RequestInit = {}) {
    const url = `${API_BASE_URL}${endpoint}`;
    const config = {
      headers: {
        'Content-Type': 'application/json',
        ...options.headers,
      },
      ...options,
    };

    const response = await fetch(url, config);
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    return response.json();
  }

  async getUsers(): Promise<User[]> {
    return this.request('/users');
  }

  async createUser(userData: Partial<User>): Promise<User> {
    return this.request('/users', {
      method: 'POST',
      body: JSON.stringify(userData),
    });
  }

  async updateUser(id: string, userData: Partial<User>): Promise<User> {
    return this.request(`/users/${id}`, {
      method: 'PUT',
      body: JSON.stringify(userData),
    });
  }
}

export const apiService = new ApiService();

// Usage example
import { useEffect, useState } from 'react';
import { FlatList, Text, View } from 'react-native';

export default function UsersList() {
  const [users, setUsers] = useState<User[]>([]);
  const [loading, setLoading] = useState(true);

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

  const loadUsers = async () => {
    try {
      const data = await apiService.getUsers();
      setUsers(data);
    } catch (error) {
      console.error('Failed to load users:', error);
    } finally {
      setLoading(false);
    }
  };

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

  return (
    <FlatList
      data={users}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <View style={{ padding: 10 }}>
          <Text>{item.name}</Text>
          <Text>{item.email}</Text>
        </View>
      )}
    />
  );
}

Push Notifications Implementation

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

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

export class NotificationService {
  static async registerForPushNotificationsAsync() {
    let token;

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

    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('Push notification permission is required');
        return;
      }
      
      token = (await Notifications.getExpoPushTokenAsync()).data;
    } else {
      alert('Please test push notifications on a real device');
    }

    return token;
  }

  static async sendLocalNotification(title: string, body: string) {
    await Notifications.scheduleNotificationAsync({
      content: {
        title,
        body,
      },
      trigger: null,
    });
  }
}

// Usage in App.tsx
import { useEffect } from 'react';

export default function App() {
  useEffect(() => {
    NotificationService.registerForPushNotificationsAsync().then(token => {
      console.log('Push token:', token);
      // Send token to server
    });

    // Handle notification received
    const subscription = Notifications.addNotificationReceivedListener(notification => {
      console.log('Notification received:', notification);
    });

    // Handle notification tapped
    const responseSubscription = Notifications.addNotificationResponseReceivedListener(response => {
      console.log('Notification tapped:', response);
    });

    return () => {
      subscription.remove();
      responseSubscription.remove();
    };
  }, []);

  return (
    // App content
  );
}

Analytics Integration (Firebase Analytics)

// services/analytics.ts
import { initializeApp } from 'firebase/app';
import { getAnalytics, logEvent, setUserId, setUserProperties } from 'firebase/analytics';

const firebaseConfig = {
  // Firebase configuration
};

const app = initializeApp(firebaseConfig);
const analytics = getAnalytics(app);

export class AnalyticsService {
  static logCustomEvent(eventName: string, parameters?: Record<string, any>) {
    logEvent(analytics, eventName, parameters);
  }

  static logScreenView(screenName: string) {
    logEvent(analytics, 'screen_view', {
      screen_name: screenName,
      screen_class: screenName,
    });
  }

  static logUserAction(action: string, item?: string) {
    logEvent(analytics, 'user_action', {
      action,
      item,
      timestamp: new Date().toISOString(),
    });
  }

  static setUser(userId: string, properties?: Record<string, any>) {
    setUserId(analytics, userId);
    if (properties) {
      setUserProperties(analytics, properties);
    }
  }

  static logPurchase(transactionId: string, value: number, currency: string) {
    logEvent(analytics, 'purchase', {
      transaction_id: transactionId,
      value,
      currency,
    });
  }
}

// hooks/useAnalytics.ts - React Hook
import { useEffect } from 'react';
import { AnalyticsService } from '../services/analytics';

export const useScreenTracking = (screenName: string) => {
  useEffect(() => {
    AnalyticsService.logScreenView(screenName);
  }, [screenName]);
};

// Usage in component
import { useScreenTracking } from '../hooks/useAnalytics';

export default function HomeScreen() {
  useScreenTracking('Home');

  const handleButtonPress = () => {
    AnalyticsService.logUserAction('button_press', 'home_cta');
    // Button action
  };

  return (
    // UI content
  );
}

Production Deployment (EAS Build)

# Install EAS CLI
npm install -g eas-cli

# Login to EAS
eas login

# Project configuration
eas build:configure

# Development Build
eas build --platform android --profile development
eas build --platform ios --profile development

# Production Build
eas build --platform android --profile production
eas build --platform ios --profile production

# Automatic submission to App Store / Google Play Store
eas submit --platform android
eas submit --platform ios
// eas.json - EAS configuration file
{
  "cli": {
    "version": ">= 7.8.0"
  },
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal",
      "ios": {
        "resourceClass": "m-medium"
      }
    },
    "preview": {
      "distribution": "internal",
      "ios": {
        "simulator": true
      }
    },
    "production": {
      "ios": {
        "resourceClass": "m-medium"
      }
    }
  },
  "submit": {
    "production": {
      "ios": {
        "appleId": "[email protected]",
        "ascAppId": "1234567890",
        "appleTeamId": "ABCDEFG123"
      },
      "android": {
        "serviceAccountKeyPath": "./path-to-google-service-account.json",
        "track": "production"
      }
    }
  }
}
// app.config.ts - App configuration
export default {
  expo: {
    name: "My Mobile App",
    slug: "my-mobile-app",
    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.mymobileapp"
    },
    android: {
      adaptiveIcon: {
        foregroundImage: "./assets/adaptive-icon.png",
        backgroundColor: "#FFFFFF"
      },
      package: "com.yourcompany.mymobileapp"
    },
    web: {
      favicon: "./assets/favicon.png"
    },
    extra: {
      eas: {
        projectId: "your-eas-project-id"
      }
    },
    updates: {
      url: "https://u.expo.dev/your-eas-project-id"
    },
    runtimeVersion: {
      policy: "sdkVersion"
    }
  }
};