Building a Food Delivery App with React Native and Firebase
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.

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.
Recommended approach
- 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
-
Create a TypeScript Expo project:
npx create-expo-app@latest FoodDeliveryApp --template blank-typescript
cd FoodDeliveryApp -
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-reanimatedExpo resolves native package versions that match the selected SDK. Keep payment and order authority on the backend.
-
Test the project: Run the app to ensure the setup works:
npx expo startPress
afor Android orifor iOS (macOS only) to launch the app on an emulator or device. You should see a blank Expo app.
Step 2: Configure Firebase
-
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).
-
Enable Authentication:
- Navigate to Authentication > Sign-in method.
- Enable Email/Password as the sign-in provider, similar to authentication setups in Instaflutter templates.
-
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.
-
Add Firebase Config to the Project:
- In the Firebase Console, go to Project Overview > Web app > Firebase SDK snippet.
- Copy the
firebaseConfigobject. - Create a file
src/firebase.ts:Replace the placeholders with your Firebase project’s config values.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 };
-
Add Firebase to Android and iOS:
- Android: Download the
google-services.jsonfile from Project Settings > Your apps > Android. Place it inFoodDeliveryApp/android/app/. - iOS: Download the
GoogleService-Info.plistfile and place it inFoodDeliveryApp/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'
- Android: Download the
Step 3: Set Up Navigation
-
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>
);
} -
Update App Entry Point: Modify
App.tsxto use the navigator:import React from 'react';
import AppNavigator from './src/navigation/AppNavigator';
export default function App() {
return <AppNavigator />;
}
Step 4: Implement Authentication Screens
-
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 },
}); -
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
-
Create Restaurant List Screen: Create
src/screens/RestaurantListScreen.tsxto 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 },
}); -
Seed Restaurant Data: In the Firebase Console, create a
restaurantscollection 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
-
Create Menu Screen: Create
src/screens/MenuScreen.tsxto 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' },
}); -
Seed Menu Data: In the Firebase Console, create a
menuItemscollection 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_IDwith the ID of a restaurant document.
Step 7: Build the Order Tracking Screen
-
Create Order Tracking Screen: Create
src/screens/OrderTrackingScreen.tsxto 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 },
}); -
Explanation of Order Tracking:
- Firestore Integration: The
useEffecthook places an order and listens for real-time updates to the order’s status usingonSnapshot, similar to features in Instamobile’s UberEats clone. - Cleanup: The
unsubscribefunction 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.
- Firestore Integration: The
-
Seed Order Data: In the Firebase Console, you can manually update the
orderscollection to simulate status changes (e.g.,Preparing→Out for Delivery→Delivered) 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
-
Add Tailwind CSS (Optional): For better styling, install
tailwind-react-native-classnames:npm install tailwind-react-native-classnames@1.5.1Note: Tailwind support in React Native is evolving. You may explore
tailwind-react-native-classnamesorUnoCSSfor an alternative with better developer experience. UpdateLoginScreen.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`],
}); -
Add Icons: Use
@expo/vector-iconsfor 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
-
Run on Android:
npx expo startPress
ato launch on an Android emulator or device (API level 21+). -
Run on iOS (macOS only): Press
ito launch on an iOS simulator, ensuring Xcode 16+ is used for iOS 18 SDK compliance. -
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, andorderscollections. - 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-locationfor 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.jsonandGoogleService-Info.plistare correctly placed. Check the Firebase Console for correct API keys. - Real-Time Not Working: Verify Firestore rules and ensure
onSnapshotis set up correctly. - Build Issues: Run
npx expo-doctorand confirm that Node.js, Expo, React Native, and Reanimated versions are compatible. Runnpm installorpod installif dependencies fail. - Performance: Optimize
FlatListwithinitialNumToRenderandwindowSizeprops 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
- dopebase.com - Mobile app development resources and templates
- instamobile.io - Explore our production-ready React Native templates
- instaflutter.com - Check out our Flutter templates for cross-platform development
- iosapptemplates.com - Premium iOS app templates for quick development