Expo Mobile App Development and Deployment: A Comprehensive Guide

Published: March 28, 2022Reading time: 15 min
Femi Adigun profile picture

Femi Adigun

Senior Software Engineer & Coach

Updated January 28, 2025

Introduction to React Native and Expo

React Native is a framework for building mobile apps using JavaScript and the React library. It allows developers to build mobile apps for iOS and Android using the same codebase, making it a cost-effective and efficient solution for cross-platform development.

Expo is a toolset built around React Native that helps developers to more easily build, test, and deploy mobile apps. It provides a set of pre-built components, APIs, and services that simplify the development process, allowing developers to focus on building their app rather than configuring native build tools.

In this guide, we will explore how to set up your development environment, create a new Expo project, develop your app, and deploy it to app stores. Whether you are new to mobile development or experienced with other frameworks, Expo offers a streamlined approach to building robust mobile applications.

Why Choose Expo?

Simplified Setup

Start developing without complex native build configurations. No Xcode or Android Studio required initially.

Fast Iteration

With hot reloading and the Expo Go app, see changes instantly without rebuilding your app.

Rich Ecosystem

Access to a wide range of pre-built components and APIs for common mobile features like camera, notifications, etc.

OTA Updates

Push updates to your app without going through app store review processes (within certain limitations).

Managed vs. Bare Workflow: Expo offers two workflows: Managed (easier but with some limitations) and Bare (full access to native code). We will focus on the Managed workflow in this guide, but will touch on when and how to eject if needed.

Setting Up Your Development Environment

Prerequisites

  • Node.js (version 14 or newer)
  • npm or Yarn package manager
  • A code editor (VS Code recommended)
  • A smartphone with Expo Go app (for testing) or iOS/Android simulators

Installation

First, install the Expo CLI globally on your computer:

npm install -g expo-cli

Or if you are using Yarn:

yarn global add expo-cli

Creating a New Project

To create a new Expo project, run:

expo init MyAwesomeApp

You will be prompted to choose a template. For beginners, the "blank" template is a good starting point. After creating your project, navigate to its directory and start the development server:

cd MyAwesomeApp expo start

This will open a browser window with the Expo DevTools. You can scan the QR code with your phone's camera (iOS) or the Expo Go app (Android) to open your app on your device.

Project Structure and Configuration

Let us look at the key files and directories in a typical Expo project:

MyAwesomeApp/
├── assets/                # Images, fonts, and other static assets
├── node_modules/         # Dependencies (don't edit)
├── .gitignore            # Git ignore configuration
├── App.js                # Main application component
├── app.json              # Expo configuration
├── babel.config.js       # Babel configuration
├── package.json          # Project dependencies and scripts
└── README.md             # Project documentation

Configuring Your App

The app.json file is where you configure various aspects of your Expo app:

{
  "expo": {
    "name": "My Awesome App",          // App name displayed to users
    "slug": "my-awesome-app",          // Unique identifier for your app
    "version": "1.0.0",                // Your app version
    "orientation": "portrait",         // portrait, landscape, or default
    "icon": "./assets/icon.png",       // App icon
    "splash": {                        // Splash screen configuration
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "updates": {
      "fallbackToCacheTimeout": 0
    },
    "assetBundlePatterns": [
      "**/*"                           // Which assets to include
    ],
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "com.yourcompany.myawesomeapp"
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/adaptive-icon.png",
        "backgroundColor": "#FFFFFF"
      },
      "package": "com.yourcompany.myawesomeapp"
    },
    "web": {
      "favicon": "./assets/favicon.png"
    }
  }
}

This configuration will be used when building your app for production. You will need to fill in your own bundle identifier/package name before submitting to app stores.

Building Your App with Expo

Essential Components and APIs

Expo provides a rich set of components and APIs that make mobile development easier:

UI Components

  • View, Text, Image
  • TouchableOpacity, Button
  • ScrollView, FlatList
  • TextInput, Switch

Device APIs

  • Camera, ImagePicker
  • Location, Sensors
  • Notifications, Haptics
  • SecureStore, FileSystem

Example: Building a Simple Screen

Here is an example of a simple screen that uses some common Expo components:

import React, { useState, useEffect } from 'react';
import { StyleSheet, Text, View, Button, Image } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import { StatusBar } from 'expo-status-bar';

export default function App() {
  const [image, setImage] = useState(null);

  useEffect(() => {
    // Request permissions when component mounts
    (async () => {
      const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
      if (status !== 'granted') {
        alert('Sorry, we need camera roll permissions to make this work!');
      }
    })();
  }, []);

  const pickImage = async () => {
    let result = await ImagePicker.launchImageLibraryAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.Images,
      allowsEditing: true,
      aspect: [4, 3],
      quality: 1,
    });

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

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Image Picker Example</Text>
      
      <Button title="Pick an image from camera roll" onPress={pickImage} />
      
      {image && (
        <Image source={{ uri: image }} style={styles.image} />
      )}
      
      <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  image: {
    width: 300,
    height: 300,
    marginTop: 20,
    borderRadius: 10,
  },
});

Installing Expo Packages

To add functionality to your app, you will often need to install additional packages. With Expo, this is straightforward:

expo install expo-image-picker expo-location expo-notifications

The expo install command ensures compatible versions are installed.

Navigation in Expo Apps

Most apps need to navigate between multiple screens. React Navigation is the standard library for handling navigation in React Native and Expo apps.

Setting Up React Navigation

expo install @react-navigation/native expo install react-native-screens react-native-safe-area-context

Next, install the navigation type you need. For a stack navigator (screens that stack on top of each other):

expo install @react-navigation/stack

Example: Basic Navigation Setup

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

import HomeScreen from './screens/HomeScreen';
import DetailsScreen from './screens/DetailsScreen';

const Stack = createStackNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen 
          name="Home" 
          component={HomeScreen} 
          options={{ title: 'Welcome' }} 
        />
        <Stack.Screen 
          name="Details" 
          component={DetailsScreen} 
          options={{ title: 'Item Details' }} 
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Navigating Between Screens

// In HomeScreen.js
import React from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';

export default function HomeScreen({ navigation }) {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Home Screen</Text>
      <Button
        title="Go to Details"
        onPress={() => navigation.navigate('Details', { itemId: 86 })}
      />
    </View>
  );
}

// In DetailsScreen.js
import React from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';

export default function DetailsScreen({ route, navigation }) {
  const { itemId } = route.params;
  
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Details Screen</Text>
      <Text>Item ID: {itemId}</Text>
      <Button title="Go back" onPress={() => navigation.goBack()} />
    </View>
  );
}

Styling and UI Libraries

Styling in React Native uses a subset of CSS with some differences. You can create stylesheets using the StyleSheet API, or use inline styles for simpler cases.

Basic Styling Examples

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

export default function StyledComponent() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Hello, Expo!</Text>
      <View style={styles.card}>
        <Text style={styles.cardText}>This is a styled card</Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
    padding: 20,
    alignItems: 'center',
    justifyContent: 'center',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
    color: '#333',
  },
  card: {
    backgroundColor: 'white',
    borderRadius: 10,
    padding: 20,
    width: '100%',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3, // for Android shadow
  },
  cardText: {
    fontSize: 16,
    color: '#666',
  },
});

Popular UI Libraries

Instead of building all UI components from scratch, you can use UI libraries to speed up development:

  • React Native Paper - Material Design components for React Native
    expo install react-native-paper
  • NativeBase - Accessible, utility-first component library
    expo install native-base
  • UI Kitten - Customizable design system
    expo install @ui-kitten/components
  • React Native Elements - Cross-platform UI toolkit
    expo install react-native-elements

Testing Your Expo App

Testing is crucial for building reliable apps. Expo supports several testing approaches:

1. Manual Testing with Expo Go

The simplest way to test your app is to use the Expo Go app on your physical device. When you runexpo start, you can scan the QR code to open your app on your device.

2. Simulators and Emulators

You can also test your app on iOS simulators (macOS only) or Android emulators:

  • Press i in the Expo DevTools to open in iOS Simulator
  • Press a to open in Android Emulator

3. Unit Testing

For unit testing, you can use Jest with React Native Testing Library:

expo install jest-expo jest @testing-library/react-native

Update your package.json:

"scripts": {
  "test": "jest"
},
"jest": {
  "preset": "jest-expo",
  "transformIgnorePatterns": [
    "node_modules/(?!(jest-)?react-native|@react-native|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|@ui-kitten)"
  ]
}

Example Test

// components/Button.test.js
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import Button from './Button';

describe('Button', () => {
  it('renders correctly', () => {
    const { getByText } = render(<Button title="Press me" />);
    expect(getByText('Press me')).toBeTruthy();
  });

  it('calls onPress when pressed', () => {
    const onPressMock = jest.fn();
    const { getByText } = render(<Button title="Press me" onPress={onPressMock} />);
    
    fireEvent.press(getByText('Press me'));
    expect(onPressMock).toHaveBeenCalledTimes(1);
  });
});

4. End-to-End Testing

For E2E testing, you can use Detox with Expo:

npm install -g detox-cli expo install detox

Setting up Detox with Expo requires additional configuration. Refer to the official documentation for detailed instructions.

Building for Production and Deployment

1. Building With Expo Application Services (EAS)

Expo's recommended way to build your app for production is using EAS Build:

npm install -g eas-cli eas login eas build:configure

This will create an eas.json file in your project. Next, build your app:

eas build --platform ios # For iOS eas build --platform android # For Android

2. Configuring App Stores Metadata

Before submitting to app stores, you need to prepare:

  • App icons (configured in app.json)
  • Splash screen (configured in app.json)
  • App screenshots (taken manually or with automation)
  • Privacy policy (required for both stores)
  • App description, keywords, category

3. Submitting to App Stores

With EAS Submit, you can submit your app directly to the stores:

eas submit --platform ios # For App Store eas submit --platform android # For Google Play

Note: For iOS, you need an Apple Developer account ($99/year). For Android, you need a Google Play Developer account ($25 one-time fee).

4. Over-the-Air (OTA) Updates

One of Expo's most powerful features is the ability to push updates without going through app store reviews. This allows you to fix bugs and add features quickly.

To publish an update:

expo publish

With EAS Update (the newer approach):

eas update --branch production --message "Fixed login bug"

Important: OTA updates can only modify JavaScript and assets, not native code. If you need to update native code, you will need to submit a new build to the app stores.

To be continued...