Skip to main content

Building a Food Delivery App with React Native and Firebase

· 13 min read
Engineering Team

Build a food-delivery app around secure roles, catalog and cart state, idempotent orders, payment boundaries, realtime status, location privacy, and operational tooling. This revised guide emphasizes supported APIs, production tradeoffs, and an upgrade-friendly path without tying the advice to a calendar year.

Building a Food Delivery App with React Native and Firebase

Modern implementation baseline

Build a food-delivery app around secure roles, catalog and cart state, idempotent orders, payment boundaries, realtime status, location privacy, and operational tooling.

Use modular Firebase APIs, server-enforced Security Rules, explicit data models, and observable background jobs. Authentication state is not authorization: every database and storage path still needs a least-privilege rule.

  • Keep prices and order transitions authoritative on the backend.
  • Model customer, restaurant, courier, and admin permissions separately.
  • Test retries, duplicate taps, refunds, and unavailable inventory.

Production checklist

  • Test rules with the emulator suite and denied requests.
  • Design indexes and pagination before production traffic.
  • Handle offline writes, retries, idempotency, and subscription cleanup.

Authoritative references

This tutorial assumes basic knowledge of JavaScript and React Native. No prior Firebase experience is required. By the end, you’ll have a functional food delivery app with a modern UI, secure authentication, and real-time order updates, ready for iOS and Android deployment.


Prerequisites

Ensure you have the following installed before starting:

  • Node.js (v20.x or later, LTS recommended): Download from nodejs.org.
  • npm (v10.x or later, included with Node.js).
  • Expo CLI: For streamlined React Native project setup.
  • Android Studio: Use the current stable release with compatible Android SDK and NDK tooling.
  • Xcode: Use the current stable release and SDK required by App Store Connect.
  • Firebase Account: Sign up at firebase.google.com.
  • A code editor like VS Code.
  • Git: For cloning repositories or managing code.

Step 1: Set Up the React Native Project with Expo

  1. Create a TypeScript Expo project:

    npx create-expo-app@latest FoodDeliveryApp --template blank-typescript
    cd FoodDeliveryApp
  2. Install compatible dependencies:

    npx expo install firebase @react-navigation/native @react-navigation/native-stack @rneui/themed @rneui/base expo-constants react-native-safe-area-context react-native-screens react-native-reanimated

    Expo resolves native package versions that match the selected SDK. Keep payment and order authority on the backend.

  3. Test the project: Run the app to ensure the setup works:

    npx expo start

    Press a for Android or i for iOS (macOS only) to launch the app on an emulator or device. You should see a blank Expo app.


Step 2: Configure Firebase

  1. Create a Firebase Project:

    • Go to the Firebase Console.
    • Click Add Project, name it FoodDeliveryApp, and follow the prompts to create it.
    • Click the Web icon under Project Overview to register a web app (even for mobile).
  2. Enable Authentication:

    • Navigate to Authentication > Sign-in method.
    • Enable Email/Password as the sign-in provider, similar to authentication setups in Instaflutter templates.
  3. Set Up Firestore:

    • Navigate to Firestore Database > Create Database.
    • Choose Start in test mode (for development) and select a region (e.g., us-central1).
    • This creates a real-time NoSQL database for storing restaurants, menu items, and orders, as used in Dopebase solutions.
  4. Add Firebase Config to the Project:

    • In the Firebase Console, go to Project Overview > Web app > Firebase SDK snippet.
    • Copy the firebaseConfig object.
    • Create a file src/firebase.ts:
      import { initializeApp } from 'firebase/app';
      import { getAuth } from 'firebase/auth';
      import { getFirestore } from 'firebase/firestore';

      const firebaseConfig = {
      apiKey: "YOUR_API_KEY",
      authDomain: "YOUR_AUTH_DOMAIN",
      projectId: "YOUR_PROJECT_ID",
      storageBucket: "YOUR_STORAGE_BUCKET",
      messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
      appId: "YOUR_APP_ID"
      };

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

      export { auth, db };
      Replace the placeholders with your Firebase project’s config values.
  5. Add Firebase to Android and iOS:

    • Android: Download the google-services.json file from Project Settings > Your apps > Android. Place it in FoodDeliveryApp/android/app/.
    • iOS: Download the GoogleService-Info.plist file and place it in FoodDeliveryApp/ios/. Update the iOS project by running:
      cd ios && pod install
    • In android/build.gradle, add:
      dependencies {
      classpath 'com.google.gms:google-services:4.4.2'
      }
    • In android/app/build.gradle, add at the bottom:
      apply plugin: 'com.google.gms.google-services'

Step 3: Set Up Navigation

  1. Configure Navigation: Create a navigation stack for the login, signup, restaurant list, menu, and order tracking screens. Create src/navigation/AppNavigator.tsx:

    import React from 'react';
    import { NavigationContainer } from '@react-navigation/native';
    import { createNativeStackNavigator } from '@react-navigation/native-stack';
    import LoginScreen from '../screens/LoginScreen';
    import SignupScreen from '../screens/SignupScreen';
    import RestaurantListScreen from '../screens/RestaurantListScreen';
    import MenuScreen from '../screens/MenuScreen';
    import OrderTrackingScreen from '../screens/OrderTrackingScreen';

    const Stack = createNativeStackNavigator();

    export default function AppNavigator() {
    return (
    <NavigationContainer>
    <Stack.Navigator initialRouteName="Login">
    <Stack.Screen name="Login" component={LoginScreen} />
    <Stack.Screen name="Signup" component={SignupScreen} />
    <Stack.Screen name="RestaurantList" component={RestaurantListScreen} />
    <Stack.Screen name="Menu" component={MenuScreen} />
    <Stack.Screen name="OrderTracking" component={OrderTrackingScreen} />
    </Stack.Navigator>
    </NavigationContainer>
    );
    }
  2. Update App Entry Point: Modify App.tsx to use the navigator:

    import React from 'react';
    import AppNavigator from './src/navigation/AppNavigator';

    export default function App() {
    return <AppNavigator />;
    }

Step 4: Implement Authentication Screens

  1. Create Login Screen: Create src/screens/LoginScreen.tsx:

    import React, { useState } from 'react';
    import { View, Text, TextInput, Button, StyleSheet, Alert } from 'react-native';
    import { signInWithEmailAndPassword } from 'firebase/auth';
    import { auth } from '../firebase';
    import { useNavigation } from '@react-navigation/native';

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

    const handleLogin = async () => {
    try {
    await signInWithEmailAndPassword(auth, email, password);
    navigation.navigate('RestaurantList');
    } catch (error) {
    Alert.alert('Login Failed', error.message);
    }
    };

    return (
    <View style={styles.container}>
    <Text style={styles.title}>Login</Text>
    <TextInput
    style={styles.input}
    placeholder="Email"
    value={email}
    onChangeText={setEmail}
    autoCapitalize="none"
    />
    <TextInput
    style={styles.input}
    placeholder="Password"
    value={password}
    onChangeText={setPassword}
    secureTextEntry
    />
    <Button title="Login" onPress={handleLogin} />
    <Button
    title="Go to Signup"
    onPress={() => navigation.navigate('Signup')}
    />
    </View>
    );
    }

    const styles = StyleSheet.create({
    container: { flex: 1, justifyContent: 'center', padding: 20 },
    title: { fontSize: 24, marginBottom: 20, textAlign: 'center' },
    input: { borderWidth: 1, padding: 10, marginBottom: 10, borderRadius: 5 },
    });
  2. Create Signup Screen: Create src/screens/SignupScreen.tsx:

    import React, { useState } from 'react';
    import { View, Text, TextInput, Button, StyleSheet, Alert } from 'react-native';
    import { createUserWithEmailAndPassword } from 'firebase/auth';
    import { auth } from '../firebase';
    import { useNavigation } from '@react-navigation/native';

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

    const handleSignup = async () => {
    try {
    await createUserWithEmailAndPassword(auth, email, password);
    navigation.navigate('RestaurantList');
    } catch (error) {
    Alert.alert('Signup Failed', error.message);
    }
    };

    return (
    <View style={styles.container}>
    <Text style={styles.title}>Sign Up</Text>
    <TextInput
    style={styles.input}
    placeholder="Email"
    value={email}
    onChangeText={setEmail}
    autoCapitalize="none"
    />
    <TextInput
    style={styles.input}
    placeholder="Password"
    value={password}
    onChangeText={setPassword}
    secureTextEntry
    />
    <Button title="Sign Up" onPress={handleSignup} />
    <Button
    title="Go to Login"
    onPress={() => navigation.navigate('Login')}
    />
    </View>
    );
    }

    const styles = StyleSheet.create({
    container: { flex: 1, justifyContent: 'center', padding: 20 },
    title: { fontSize: 24, marginBottom: 20, textAlign: 'center' },
    input: { borderWidth: 1, padding: 10, marginBottom: 10, borderRadius: 5 },
    });

Step 5: Build the Restaurant List Screen

  1. Create Restaurant List Screen: Create src/screens/RestaurantListScreen.tsx to display a list of restaurants from Firestore:

    import React, { useState, useEffect } from 'react';
    import { View, Text, FlatList, StyleSheet, TouchableOpacity } from 'react-native';
    import { collection, query, onSnapshot } from 'firebase/firestore';
    import { db, auth } from '../firebase';
    import { useNavigation } from '@react-navigation/native';
    import { Button } from '@rneui/themed';
    import { signOut } from 'firebase/auth';

    export default function RestaurantListScreen() {
    const [restaurants, setRestaurants] = useState([]);
    const navigation = useNavigation();

    useEffect(() => {
    const q = query(collection(db, 'restaurants'));
    const unsubscribe = onSnapshot(q, (snapshot) => {
    const restaurantList = snapshot.docs.map((doc) => ({
    id: doc.id,
    ...doc.data(),
    }));
    setRestaurants(restaurantList);
    });

    return () => unsubscribe();
    }, []);

    const handleLogout = async () => {
    await signOut(auth);
    navigation.navigate('Login');
    };

    return (
    <View style={styles.container}>
    <Text style={styles.title}>Restaurants</Text>
    <FlatList
    data={restaurants}
    keyExtractor={(item) => item.id}
    renderItem={({ item }) => (
    <TouchableOpacity
    style={styles.item}
    onPress={() => navigation.navigate('Menu', { restaurantId: item.id })}
    >
    <Text style={styles.itemText}>{item.name}</Text>
    </TouchableOpacity>
    )}
    />
    <Button title="Logout" onPress={handleLogout} containerStyle={styles.logoutButton} />
    </View>
    );
    }

    const styles = StyleSheet.create({
    container: { flex: 1, padding: 20 },
    title: { fontSize: 24, marginBottom: 20, textAlign: 'center' },
    item: { padding: 15, borderBottomWidth: 1, borderBottomColor: '#ccc' },
    itemText: { fontSize: 18 },
    logoutButton: { marginTop: 20 },
    });
  2. Seed Restaurant Data: In the Firebase Console, create a restaurants collection with sample documents, e.g.:

    {
    "name": "Pizza Palace",
    "description": "Delicious pizzas and pastas",
    "image": "https://example.com/pizza.jpg"
    },
    {
    "name": "Burger Bonanza",
    "description": "Juicy burgers and fries",
    "image": "https://example.com/burger.jpg"
    }

Step 6: Build the Menu Screen

  1. Create Menu Screen: Create src/screens/MenuScreen.tsx to display a restaurant’s menu items:

    import React, { useState, useEffect } from 'react';
    import { View, Text, FlatList, StyleSheet, TouchableOpacity } from 'react-native';
    import { collection, query, where, onSnapshot } from 'firebase/firestore';
    import { db } from '../firebase';
    import { useNavigation, useRoute } from '@react-navigation/native';
    import { Button } from '@rneui/themed';

    export default function MenuScreen() {
    const [menuItems, setMenuItems] = useState([]);
    const navigation = useNavigation();
    const route = useRoute();
    const { restaurantId } = route.params;

    useEffect(() => {
    const q = query(collection(db, 'menuItems'), where('restaurantId', '==', restaurantId));
    const unsubscribe = onSnapshot(q, (snapshot) => {
    const menuList = snapshot.docs.map((doc) => ({
    id: doc.id,
    ...doc.data(),
    }));
    setMenuItems(menuList);
    });

    return () => unsubscribe();
    }, [restaurantId]);

    const handleOrder = (item) => {
    navigation.navigate('OrderTracking', { itemId: item.id, restaurantId });
    };

    return (
    <View style={styles.container}>
    <Text style={styles.title}>Menu</Text>
    <FlatList
    data={menuItems}
    keyExtractor={(item) => item.id}
    renderItem={({ item }) => (
    <View style={styles.item}>
    <Text style={styles.itemText}>{item.name} - ${item.price}</Text>
    <Button
    title="Order"
    onPress={() => handleOrder(item)}
    buttonStyle={styles.orderButton}
    />
    </View>
    )}
    />
    </View>
    );
    }

    const styles = StyleSheet.create({
    container: { flex: 1, padding: 20 },
    title: { fontSize: 24, marginBottom: 20, textAlign: 'center' },
    item: { padding: 15, borderBottomWidth: 1, borderBottomColor: '#ccc', flexDirection: 'row', justifyContent: 'space-between' },
    itemText: { fontSize: 18 },
    orderButton: { backgroundColor: '#28a745' },
    });
  2. Seed Menu Data: In the Firebase Console, create a menuItems collection with sample documents, e.g.:

    {
    "restaurantId": "RESTAURANT_ID",
    "name": "Margherita Pizza",
    "price": 12.99,
    "description": "Classic pizza with tomato and mozzarella"
    },
    {
    "restaurantId": "RESTAURANT_ID",
    "name": "Cheeseburger",
    "price": 8.99,
    "description": "Beef patty with cheddar and lettuce"
    }

    Replace RESTAURANT_ID with the ID of a restaurant document.


Step 7: Build the Order Tracking Screen

  1. Create Order Tracking Screen: Create src/screens/OrderTrackingScreen.tsx to track orders in real-time:

    import React, { useState, useEffect } from 'react';
    import { View, Text, StyleSheet } from 'react-native';
    import { collection, addDoc, doc, onSnapshot } from 'firebase/firestore';
    import { db, auth } from '../firebase';
    import { useRoute } from '@react-navigation/native';

    export default function OrderTrackingScreen() {
    const [order, setOrder] = useState(null);
    const route = useRoute();
    const { itemId, restaurantId } = route.params;

    useEffect(() => {
    // Simulate placing an order
    const placeOrder = async () => {
    const orderRef = await addDoc(collection(db, 'orders'), {
    userId: auth.currentUser.uid,
    itemId,
    restaurantId,
    status: 'Preparing',
    createdAt: new Date(),
    });

    const unsubscribe = onSnapshot(doc(db, 'orders', orderRef.id), (doc) => {
    setOrder({ id: doc.id, ...doc.data() });
    });

    return () => unsubscribe();
    };

    placeOrder();
    }, [itemId, restaurantId]);

    return (
    <View style={styles.container}>
    <Text style={styles.title}>Order Tracking</Text>
    {order ? (
    <View>
    <Text style={styles.text}>Order ID: {order.id}</Text>
    <Text style={styles.text}>Status: {order.status}</Text>
    </View>
    ) : (
    <Text style={styles.text}>Placing order...</Text>
    )}
    </View>
    );
    }

    const styles = StyleSheet.create({
    container: { flex: 1, padding: 20, justifyContent: 'center' },
    title: { fontSize: 24, marginBottom: 20, textAlign: 'center' },
    text: { fontSize: 18, marginBottom: 10 },
    });
  2. Explanation of Order Tracking:

    • Firestore Integration: The useEffect hook places an order and listens for real-time updates to the order’s status using onSnapshot, similar to features in Instamobile’s UberEats clone.
    • Cleanup: The unsubscribe function prevents memory leaks by stopping the Firestore listener when the component unmounts.
    • Order Simulation: For simplicity, the order is placed automatically. In a production app, integrate a cart system and payment gateway like Stripe.
  3. Seed Order Data: In the Firebase Console, you can manually update the orders collection to simulate status changes (e.g., PreparingOut for DeliveryDelivered) for testing.


Step 8: Secure Firestore Rules

Update Firestore rules in the Firebase Console to ensure secure access to data:

rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /restaurants/{document=**} {
allow read: if true;
allow write: if request.auth != null;
}
match /menuItems/{document=**} {
allow read: if true;
allow write: if request.auth != null;
}
match /orders/{document=**} {
allow read: if request.auth != null && request.auth.uid == resource.data.userId;
allow write: if request.auth != null
&& request.resource.data.userId == request.auth.uid
&& request.resource.data.itemId is string
&& request.resource.data.restaurantId is string
&& request.resource.data.status is string
&& request.resource.data.createdAt is timestamp;
}
}
}

Note: Before deploying to production, ensure these rules validate field types and timestamps to maintain data integrity, as seen in Dopebase templates.


Step 9: Style and Enhance the App

  1. Add Tailwind CSS (Optional): For better styling, install tailwind-react-native-classnames:

    npm install tailwind-react-native-classnames@1.5.1

    Note: Tailwind support in React Native is evolving. You may explore tailwind-react-native-classnames or UnoCSS for an alternative with better developer experience. Update LoginScreen.tsx, SignupScreen.tsx, and other screens with Tailwind classes:

    import tw from 'tailwind-react-native-classnames';

    const styles = StyleSheet.create({
    container: [tw`flex-1 justify-center p-5`],
    title: [tw`text-2xl mb-5 text-center`],
    input: [tw`border p-2 mb-2 rounded`],
    });
  2. Add Icons: Use @expo/vector-icons for icons in the restaurant and menu screens:

    import { MaterialIcons as Icon } from '@expo/vector-icons';

    // Example in RestaurantListScreen.tsx
    <Icon name="restaurant" size={24} color="#000" />

Step 10: Build and Test the App

  1. Run on Android:

    npx expo start

    Press a to launch on an Android emulator or device (API level 21+).

  2. Run on iOS (macOS only): Press i to launch on an iOS simulator, ensuring Xcode 16+ is used for iOS 18 SDK compliance.

  3. Test the App:

    • Sign up or log in with an email and password.
    • Browse restaurants and select a menu item to place an order.
    • Track the order status in real-time, similar to features in Instamobile’s UberEats clone.
    • Verify users in the Firebase Console under Authentication > Users.
    • Check data in Firestore Database for restaurants, menuItems, and orders collections.
    • Test logout and navigation between screens.

Step 11: Enhance the App

  • Add Cart System: Implement a cart to allow multiple item orders, as seen in Instaflutter templates.
  • Integrate Payments: Use Stripe or Firebase Cloud Functions for payment processing.
  • Add Location Services: Integrate expo-location for restaurant filtering by proximity.
  • Push Notifications: Implement Firebase Cloud Messaging (FCM) for order status updates, as included in Dopebase solutions.

Troubleshooting Tips

  • Firebase Errors: Ensure google-services.json and GoogleService-Info.plist are correctly placed. Check the Firebase Console for correct API keys.
  • Real-Time Not Working: Verify Firestore rules and ensure onSnapshot is set up correctly.
  • Build Issues: Run npx expo-doctor and confirm that Node.js, Expo, React Native, and Reanimated versions are compatible. Run npm install or pod install if dependencies fail.
  • Performance: Optimize FlatList with initialNumToRender and windowSize props for large restaurant or menu lists.

Conclusion

You’ve built a food delivery app using React Native and Firebase, leveraging the current Expo and Firebase tooling. This app supports user authentication, restaurant browsing, food ordering, and real-time order tracking, making it a solid foundation for further enhancements. For faster development, explore prebuilt solutions like Instamobile’s UberEats clone or customizable templates at Dopebase and Instaflutter, which offer production-ready codebases. Dive into Firebase’s documentation for advanced features like Cloud Functions or Storage, and check out React Native’s community resources at reactnative.dev for additional libraries.

For further help, join the Firebase community on Stack Overflow

Additional Resources