🟫 ClodHost beta
Sign In
🎉 All services free during beta! 🎉 All services free during beta!

Building a Mobile App with Expo

Create iOS and Android apps with React Native and Expo. Write once, deploy to both platforms.

Reading time: 45 min Difficulty: Advanced
View examples as:
Ask Claude Manual Code

Table of Contents

Why Expo?

Expo is a framework built on top of React Native that makes mobile development much easier:

When to Use Expo

Expo is perfect for most apps. You only need "bare" React Native if you require custom native modules that Expo doesn't support.

Project Setup

Let's create a new Expo project.

Create Project

Example prompt:

"Create a new Expo React Native project with TypeScript. Set up the folder structure with screens, components, and services directories. Include navigation (React Navigation) with tab-based navigation for Home, Search, and Profile screens."
# Create new Expo project with TypeScript
npx create-expo-app@latest myapp --template blank-typescript
cd myapp

# Install navigation
npm install @react-navigation/native @react-navigation/bottom-tabs
npm install react-native-screens react-native-safe-area-context

# Create folder structure
mkdir src
mkdir src/screens src/components src/services src/hooks

# Start development
npx expo start

Testing on Your Phone

  1. Install Expo Go app from App Store (iOS) or Play Store (Android)
  2. Run npx expo start in your project
  3. Scan the QR code with your phone's camera
  4. The app opens in Expo Go - changes update live!

Building UI Components

React Native uses different components than web React, but the concepts are the same.

Basic Components

Example prompt:

"Create a ProductCard component that shows an image, product name, price, and an 'Add to Cart' button. Make it look nice with shadows and rounded corners. Use React Native StyleSheet."
// components/ProductCard.tsx
import { View, Text, Image, TouchableOpacity, StyleSheet } from 'react-native';

interface Props {
    name: string;
    price: number;
    image: string;
    onAddToCart: () => void;
}

export function ProductCard({ name, price, image, onAddToCart }: Props) {
    return (
        <View style={styles.card}>
            <Image source={{ uri: image }} style={styles.image} />
            <View style={styles.info}>
                <Text style={styles.name}>{name}</Text>
                <Text style={styles.price}>${price.toFixed(2)}</Text>
            </View>
            <TouchableOpacity style={styles.button} onPress={onAddToCart}>
                <Text style={styles.buttonText}>Add to Cart</Text>
            </TouchableOpacity>
        </View>
    );
}

const styles = StyleSheet.create({
    card: {
        backgroundColor: '#fff',
        borderRadius: 12,
        padding: 16,
        marginBottom: 16,
        shadowColor: '#000',
        shadowOffset: { width: 0, height: 2 },
        shadowOpacity: 0.1,
        shadowRadius: 4,
        elevation: 3,
    },
    image: { width: '100%', height: 150, borderRadius: 8 },
    info: { marginTop: 12 },
    name: { fontSize: 18, fontWeight: '600' },
    price: { fontSize: 16, color: '#14b8a6', marginTop: 4 },
    button: { backgroundColor: '#14b8a6', padding: 12, borderRadius: 8, marginTop: 12 },
    buttonText: { color: '#fff', textAlign: 'center', fontWeight: '600' },
});

React Navigation handles moving between screens.

Tab Navigation

Example prompt:

"Set up tab navigation with Home, Search, Cart, and Profile tabs. Use icons from @expo/vector-icons. The Cart tab should show a badge with the number of items. Style the tab bar with a dark theme."
// App.tsx
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons';

const Tab = createBottomTabNavigator();

export default function App() {
    return (
        <NavigationContainer>
            <Tab.Navigator
                screenOptions={({ route }) => ({
                    tabBarIcon: ({ focused, color, size }) => {
                        let iconName;
                        if (route.name === 'Home') iconName = 'home';
                        else if (route.name === 'Search') iconName = 'search';
                        else if (route.name === 'Cart') iconName = 'cart';
                        else if (route.name === 'Profile') iconName = 'person';
                        return <Ionicons name={iconName} size={size} color={color} />;
                    },
                    tabBarActiveTintColor: '#14b8a6',
                    tabBarInactiveTintColor: '#666',
                    tabBarStyle: { backgroundColor: '#1a1a1a', borderTopColor: '#333' },
                })}
            >
                <Tab.Screen name="Home" component={HomeScreen} />
                <Tab.Screen name="Search" component={SearchScreen} />
                <Tab.Screen name="Cart" component={CartScreen} options={{ tabBarBadge: 3 }} />
                <Tab.Screen name="Profile" component={ProfileScreen} />
            </Tab.Navigator>
        </NavigationContainer>
    );
}

Connecting to Your API

Fetch data from your ClodHost backend.

API Service

Example prompt:

"Create an API service for my React Native app that connects to my backend at https://api.myapp.com. Include functions for authentication (login, register, logout) and product fetching. Store the auth token securely using expo-secure-store."
// services/api.ts
import * as SecureStore from 'expo-secure-store';

const API_URL = 'https://api.myapp.com';

async function getToken() {
    return await SecureStore.getItemAsync('authToken');
}

async function request(endpoint: string, options: RequestInit = {}) {
    const token = await getToken();

    const response = await fetch(`${API_URL}${endpoint}`, {
        ...options,
        headers: {
            'Content-Type': 'application/json',
            ...(token && { Authorization: `Bearer ${token}` }),
            ...options.headers,
        },
    });

    if (!response.ok) throw new Error('API Error');
    return response.json();
}

export const api = {
    login: async (email: string, password: string) => {
        const data = await request('/auth/login', {
            method: 'POST',
            body: JSON.stringify({ email, password }),
        });
        await SecureStore.setItemAsync('authToken', data.token);
        return data;
    },
    getProducts: () => request('/products'),
    logout: async () => {
        await SecureStore.deleteItemAsync('authToken');
    },
};

Native Features

Expo provides easy access to device features.

Camera & Photos

Example prompt:

"Add a profile photo picker to my app. Let users either take a photo with the camera or pick from their gallery. After selecting, upload the image to my API endpoint /api/upload-avatar."
import * as ImagePicker from 'expo-image-picker';

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

    if (!result.canceled) {
        await uploadImage(result.assets[0].uri);
    }
}

async function takePhoto() {
    const { status } = await ImagePicker.requestCameraPermissionsAsync();
    if (status !== 'granted') {
        alert('Camera permission required');
        return;
    }

    const result = await ImagePicker.launchCameraAsync({
        allowsEditing: true,
        aspect: [1, 1],
    });

    if (!result.canceled) {
        await uploadImage(result.assets[0].uri);
    }
}

Testing on Devices

Publishing to App Stores

Build for Stores

Example prompt:

"Help me build my Expo app for the App Store and Play Store. Configure app.json with the right settings (name, icon, splash screen, bundle identifier). Use EAS Build to create the production builds."
# Install EAS CLI
npm install -g eas-cli

# Login to Expo
eas login

# Configure EAS
eas build:configure

# Build for iOS (requires Apple Developer account)
eas build --platform ios --profile production

# Build for Android
eas build --platform android --profile production

# Submit to stores
eas submit --platform ios
eas submit --platform android
App Store Requirements

You need an Apple Developer account ($99/year) for iOS and a Google Play Developer account ($25 one-time) for Android. Review each store's guidelines before submitting.