Popular Searches
Popular Course Categories
Popular Courses

Build a Flutter Food Delivery App

Build a Flutter Food Delivery App

Flutter Practical Projects


Build a Flutter Food Delivery App


A Flutter Food Delivery App is a practical project that demonstrates how to build a real-world food ordering application using Flutter and Dart. The application can include restaurant listings, food categories, menus, food details, search, cart management, address selection, checkout, order tracking, authentication, and backend/API integration.


This project combines Flutter UI development, navigation, state management, asynchronous programming, API integration, JSON handling, forms, local storage, and responsive design. Flutter supports HTTP networking through packages such as http, while application state can be shared between screens using an appropriate state-management approach. :contentReference[oaicite:0]{index=0}




1. Objectives of the Food Delivery App



  • Create a complete food delivery application using Flutter.

  • Display restaurants and food items.

  • Organize food items into categories.

  • Display restaurant menus.

  • Show detailed food information.

  • Implement food search and filtering.

  • Add food items to a shopping cart.

  • Increase and decrease food quantities.

  • Calculate subtotal, delivery charges, discounts, and final total.

  • Implement wishlist or favorite restaurants and foods.

  • Create user login and registration screens.

  • Manage delivery addresses.

  • Create a checkout screen.

  • Place food orders.

  • Display order history and order status.

  • Integrate APIs or Firebase for backend functionality.

  • Handle loading, empty, success, and error states.

  • Create a responsive interface for different screen sizes.




2. Technologies Used












TechnologyPurpose
FlutterBuild the cross-platform application interface.
DartProgramming language used to develop the application.
Material DesignCreate modern user-interface components.
HTTPCommunicate with REST APIs.
JSONExchange structured data between the application and server.
ProviderOne possible approach for managing shared application state.
FirebaseCan provide authentication, database, storage, and other backend services.
SharedPreferencesStore small local preferences and application settings.

The Flutter documentation recommends the http package as a simple way to make cross-platform HTTP requests. :contentReference[oaicite:1]{index=1}




3. Main Features



  • Splash Screen

  • Onboarding Screen

  • Login and Registration

  • Home Screen

  • Restaurant Listing

  • Restaurant Details

  • Food Categories

  • Food Menu

  • Food Details

  • Search Food

  • Filter and Sort

  • Favorite Restaurants

  • Favorite Food Items

  • Shopping Cart

  • Quantity Management

  • Delivery Address

  • Checkout

  • Payment Interface

  • Order Confirmation

  • Order History

  • Order Tracking

  • User Profile

  • Notifications

  • API Integration




4. Food Delivery App Flow


Launch App
    ↓
Splash Screen
    ↓
Login / Register
    ↓
Home Screen
    ↓
Search / Categories
    ↓
Restaurant Listing
    ↓
Restaurant Details
    ↓
Food Menu
    ↓
Food Details
    ↓
Add to Cart
    ↓
Cart
    ↓
Delivery Address
    ↓
Checkout
    ↓
Payment
    ↓
Order Confirmation
    ↓
Order Tracking
    ↓
Order History



5. Recommended Project Structure


lib/
├── main.dart
├── models/
│   ├── restaurant.dart
│   ├── food.dart
│   ├── category.dart
│   ├── cart_item.dart
│   ├── address.dart
│   └── order.dart
├── screens/
│   ├── splash_screen.dart
│   ├── login_screen.dart
│   ├── register_screen.dart
│   ├── home_screen.dart
│   ├── restaurant_screen.dart
│   ├── food_details_screen.dart
│   ├── cart_screen.dart
│   ├── checkout_screen.dart
│   ├── address_screen.dart
│   ├── orders_screen.dart
│   └── profile_screen.dart
├── services/
│   ├── restaurant_service.dart
│   ├── food_service.dart
│   ├── auth_service.dart
│   └── order_service.dart
├── providers/
│   ├── cart_provider.dart
│   ├── restaurant_provider.dart
│   ├── favorite_provider.dart
│   └── auth_provider.dart
├── widgets/
│   ├── restaurant_card.dart
│   ├── food_card.dart
│   ├── category_card.dart
│   └── cart_item_widget.dart
└── utils/
    └── constants.dart

Separating models, UI screens, reusable widgets, services, and state-management classes helps keep a growing application easier to maintain.




6. Create the Flutter Project


Create a new Flutter project using the terminal:


flutter create food_delivery_app
cd food_delivery_app
flutter run

The flutter create command creates the basic Flutter project structure.




7. Add Required Packages


Packages can be added according to the features required by the project.


flutter pub add http
flutter pub add provider
flutter pub add shared_preferences
flutter pub add cached_network_image
flutter pub add url_launcher

The http package can be used for fetching and sending data to a backend API. :contentReference[oaicite:2]{index=2}




8. Create the Main Application


import 'package:flutter/material.dart';

void main() {
  runApp(const FoodDeliveryApp());
}

class FoodDeliveryApp extends StatelessWidget {
  const FoodDeliveryApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Food Delivery App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.orange,
        ),
        useMaterial3: true,
      ),
      home: const HomeScreen(),
    );
  }
}




9. Designing the Home Screen


The home screen is the main area where users discover restaurants and food items. It can contain a search field, location information, food categories, promotional banners, popular restaurants, and recommended foods.


class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Food Delivery'),
        actions: [
          IconButton(
            onPressed: () {},
            icon: const Icon(Icons.shopping_cart_outlined),
          ),
        ],
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          const Text(
            'What would you like to eat?',
            style: TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 16),
          TextField(
            decoration: InputDecoration(
              hintText: 'Search restaurants or food',
              prefixIcon: const Icon(Icons.search),
              border: OutlineInputBorder(
                borderRadius: BorderRadius.circular(12),
              ),
            ),
          ),
        ],
      ),
    );
  }
}




10. Location Section


A food delivery application commonly displays the current delivery location or a selected address at the top of the home screen.


Row(
  children: [
    const Icon(Icons.location_on),
    const SizedBox(width: 8),
    const Expanded(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Deliver to',
            style: TextStyle(fontSize: 12),
          ),
          Text(
            'Home Address',
            style: TextStyle(
              fontWeight: FontWeight.bold,
            ),
          ),
        ],
      ),
    ),
    IconButton(
      onPressed: () {},
      icon: const Icon(Icons.keyboard_arrow_down),
    ),
  ],
)



11. Food Category Model


Categories allow users to quickly find food types such as Pizza, Burgers, Indian Food, Chinese Food, Desserts, and Beverages.


class FoodCategory {
  final int id;
  final String name;
  final String image;

  FoodCategory({
    required this.id,
    required this.name,
    required this.image,
  });

  factory FoodCategory.fromJson(
    Map json,
  ) {
    return FoodCategory(
      id: json['id'],
      name: json['name'],
      image: json['image'],
    );
  }
}




12. Food Model


class Food {
  final int id;
  final String name;
  final String description;
  final double price;
  final String image;
  final String category;
  final double rating;
  final bool isVegetarian;

  Food({
    required this.id,
    required this.name,
    required this.description,
    required this.price,
    required this.image,
    required this.category,
    required this.rating,
    required this.isVegetarian,
  });

  factory Food.fromJson(
    Map json,
  ) {
    return Food(
      id: json['id'],
      name: json['name'],
      description: json['description'],
      price: (json['price'] as num).toDouble(),
      image: json['image'],
      category: json['category'],
      rating: (json['rating'] as num?)?.toDouble() ?? 0,
      isVegetarian: json['isVegetarian'] ?? false,
    );
  }
}




13. Restaurant Model


class Restaurant {
  final int id;
  final String name;
  final String image;
  final String cuisine;
  final double rating;
  final String deliveryTime;
  final double deliveryFee;

  Restaurant({
    required this.id,
    required this.name,
    required this.image,
    required this.cuisine,
    required this.rating,
    required this.deliveryTime,
    required this.deliveryFee,
  });

  factory Restaurant.fromJson(
    Map json,
  ) {
    return Restaurant(
      id: json['id'],
      name: json['name'],
      image: json['image'],
      cuisine: json['cuisine'],
      rating: (json['rating'] as num).toDouble(),
      deliveryTime: json['deliveryTime'],
      deliveryFee: (json['deliveryFee'] as num).toDouble(),
    );
  }
}




14. Restaurant Card


A restaurant card can display the restaurant image, name, cuisine, rating, delivery time, and delivery charge.


class RestaurantCard extends StatelessWidget {
  final Restaurant restaurant;

  const RestaurantCard({
    super.key,
    required this.restaurant,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      clipBehavior: Clip.antiAlias,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Image.network(
            restaurant.image,
            height: 180,
            width: double.infinity,
            fit: BoxFit.cover,
          ),
          Padding(
            padding: const EdgeInsets.all(12),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  restaurant.name,
                  style: const TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 6),
                Text(restaurant.cuisine),
                const SizedBox(height: 6),
                Row(
                  children: [
                    const Icon(
                      Icons.star,
                      size: 18,
                    ),
                    const SizedBox(width: 4),
                    Text('${restaurant.rating}'),
                    const Spacer(),
                    Text(restaurant.deliveryTime),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}




15. Food Card


class FoodCard extends StatelessWidget {
  final Food food;

  const FoodCard({
    super.key,
    required this.food,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(10),
        child: Row(
          children: [
            ClipRRect(
              borderRadius: BorderRadius.circular(10),
              child: Image.network(
                food.image,
                width: 100,
                height: 100,
                fit: BoxFit.cover,
              ),
            ),
            const SizedBox(width: 12),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    food.name,
                    style: const TextStyle(
                      fontSize: 17,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 6),
                  Text(
                    food.description,
                    maxLines: 2,
                    overflow: TextOverflow.ellipsis,
                  ),
                  const SizedBox(height: 8),
                  Text(
                    '₹${food.price.toStringAsFixed(2)}',
                    style: const TextStyle(
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}




16. Display Food Categories


ListView.builder(
  scrollDirection: Axis.horizontal,
  itemCount: categories.length,
  itemBuilder: (context, index) {
    final category = categories[index];

    return Padding(
      padding: const EdgeInsets.only(right: 12),
      child: Column(
        children: [
          CircleAvatar(
            radius: 35,
            backgroundImage: NetworkImage(
              category.image,
            ),
          ),
          const SizedBox(height: 6),
          Text(category.name),
        ],
      ),
    );
  },
)




17. Restaurant Details Screen


When a user selects a restaurant, the application can display restaurant information and its complete menu.


class RestaurantDetailsScreen extends StatelessWidget {
  final Restaurant restaurant;

  const RestaurantDetailsScreen({
    super.key,
    required this.restaurant,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(restaurant.name),
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          Image.network(
            restaurant.image,
            height: 220,
            fit: BoxFit.cover,
          ),
          const SizedBox(height: 16),
          Text(
            restaurant.name,
            style: const TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 8),
          Text(restaurant.cuisine),
          const SizedBox(height: 8),
          Text(
            '⭐ ${restaurant.rating}  •  ${restaurant.deliveryTime}',
          ),
          const SizedBox(height: 24),
          const Text(
            'Menu',
            style: TextStyle(
              fontSize: 22,
              fontWeight: FontWeight.bold,
            ),
          ),
        ],
      ),
    );
  }
}




18. Food Details Screen


class FoodDetailsScreen extends StatelessWidget {
  final Food food;

  const FoodDetailsScreen({
    super.key,
    required this.food,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Food Details'),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Image.network(
              food.image,
              height: 300,
              width: double.infinity,
              fit: BoxFit.cover,
            ),
            const SizedBox(height: 20),
            Text(
              food.name,
              style: const TextStyle(
                fontSize: 26,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 10),
            Text(
              '₹${food.price.toStringAsFixed(2)}',
              style: const TextStyle(
                fontSize: 22,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 16),
            Text(food.description),
            const SizedBox(height: 24),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: () {},
                child: const Text('Add to Cart'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}




19. Navigation Between Screens


For simple application flows, Flutter's Navigator can push a new screen onto the navigation stack.


Navigator.of(context).push(
  MaterialPageRoute(
    builder: (context) {
      return FoodDetailsScreen(
        food: food,
      );
    },
  ),
);

To return to the previous screen:


Navigator.of(context).pop();

For more advanced routing and deep-linking requirements, Flutter also supports Router-based navigation and packages such as go_router. :contentReference[oaicite:3]{index=3}




20. Shopping Cart


The cart stores the food items selected by the customer. Each cart item normally contains the selected food and quantity.


class CartItem {
  final Food food;
  int quantity;

  CartItem({
    required this.food,
    this.quantity = 1,
  });

  double get totalPrice {
    return food.price * quantity;
  }
}




21. Cart Provider


A shared state-management solution can keep cart information available across different screens.


import 'package:flutter/foundation.dart';

class CartProvider extends ChangeNotifier {
  final List _items = [];

  List get items {
    return List.unmodifiable(_items);
  }

  void addToCart(Food food) {
    final index = _items.indexWhere(
      (item) => item.food.id == food.id,
    );

    if (index != -1) {
      _items[index].quantity++;
    } else {
      _items.add(
        CartItem(food: food),
      );
    }

    notifyListeners();
  }

  void increaseQuantity(Food food) {
    final item = _items.firstWhere(
      (item) => item.food.id == food.id,
    );

    item.quantity++;
    notifyListeners();
  }

  void decreaseQuantity(Food food) {
    final item = _items.firstWhere(
      (item) => item.food.id == food.id,
    );

    if (item.quantity > 1) {
      item.quantity--;
    } else {
      _items.remove(item);
    }

    notifyListeners();
  }

  void removeFromCart(Food food) {
    _items.removeWhere(
      (item) => item.food.id == food.id,
    );

    notifyListeners();
  }

  double get subtotal {
    return _items.fold(
      0,
      (sum, item) => sum + item.totalPrice,
    );
  }
}


Flutter documentation notes that applications often need shared state between screens, and different state-management approaches can be selected according to the application's requirements. :contentReference[oaicite:4]{index=4}




22. Configure Provider


void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => CartProvider(),
      child: const FoodDeliveryApp(),
    ),
  );
}



23. Add Food to Cart


ElevatedButton(
  onPressed: () {
    context.read().addToCart(food);

    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('Food added to cart'),
      ),
    );
  },
  child: const Text('Add to Cart'),
)




24. Build Cart Screen


class CartScreen extends StatelessWidget {
  const CartScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final cart = context.watch();

    return Scaffold(
      appBar: AppBar(
        title: const Text('Your Cart'),
      ),
      body: cart.items.isEmpty
          ? const Center(
              child: Text('Your cart is empty'),
            )
          : ListView.builder(
              itemCount: cart.items.length,
              itemBuilder: (context, index) {
                final item = cart.items[index];

                return ListTile(
                  leading: Image.network(
                    item.food.image,
                    width: 60,
                    height: 60,
                    fit: BoxFit.cover,
                  ),
                  title: Text(item.food.name),
                  subtitle: Text(
                    '₹${item.totalPrice.toStringAsFixed(2)}',
                  ),
                  trailing: Text(
                    'Qty: ${item.quantity}',
                  ),
                );
              },
            ),
      bottomNavigationBar: Padding(
        padding: const EdgeInsets.all(16),
        child: Text(
          'Subtotal: ₹${cart.subtotal.toStringAsFixed(2)}',
        ),
      ),
    );
  }
}




25. Quantity Controls


Row(
  children: [
    IconButton(
      onPressed: () {
        cart.decreaseQuantity(food);
      },
      icon: const Icon(Icons.remove),
    ),
    Text('$quantity'),
    IconButton(
      onPressed: () {
        cart.increaseQuantity(food);
      },
      icon: const Icon(Icons.add),
    ),
  ],
)



26. Favorite Food Items


A favorite feature allows users to save foods or restaurants for quick access later.


class FavoriteProvider extends ChangeNotifier {
  final Set _favoriteFoods = {};

  bool isFavorite(int foodId) {
    return _favoriteFoods.contains(foodId);
  }

  void toggleFavorite(int foodId) {
    if (_favoriteFoods.contains(foodId)) {
      _favoriteFoods.remove(foodId);
    } else {
      _favoriteFoods.add(foodId);
    }

    notifyListeners();
  }
}




27. Search Food


List searchFood(
  List foods,
  String query,
) {
  if (query.trim().isEmpty) {
    return foods;
  }

  return foods.where((food) {
    return food.name
        .toLowerCase()
        .contains(query.toLowerCase());
  }).toList();
}




28. Search Restaurants


List searchRestaurants(
  List restaurants,
  String query,
) {
  if (query.trim().isEmpty) {
    return restaurants;
  }

  return restaurants.where((restaurant) {
    return restaurant.name
        .toLowerCase()
        .contains(query.toLowerCase());
  }).toList();
}




29. Food Filtering


Food can be filtered by category, vegetarian preference, price range, rating, or other attributes.


List filterVegetarianFood(
  List foods,
) {
  return foods
      .where((food) => food.isVegetarian)
      .toList();
}



30. Sorting Food


Users can sort food items according to price or rating.


foods.sort(
  (a, b) => a.price.compareTo(b.price),
);

For highest price first:


foods.sort(
  (a, b) => b.price.compareTo(a.price),
);



31. Login Screen


final emailController = TextEditingController();
final passwordController = TextEditingController();

TextField(
  controller: emailController,
  keyboardType: TextInputType.emailAddress,
  decoration: const InputDecoration(
    labelText: 'Email',
    prefixIcon: Icon(Icons.email),
  ),
)

TextField(
  controller: passwordController,
  obscureText: true,
  decoration: const InputDecoration(
    labelText: 'Password',
    prefixIcon: Icon(Icons.lock),
  ),
)




32. Registration Screen


The registration form can collect information such as name, email, phone number, and password.


TextFormField(
  decoration: const InputDecoration(
    labelText: 'Full Name',
  ),
  validator: (value) {
    if (value == null || value.trim().isEmpty) {
      return 'Name is required';
    }

    return null;
  },
)




33. Form Validation


final formKey = GlobalKey();

Form(
  key: formKey,
  child: TextFormField(
    validator: (value) {
      if (value == null || value.trim().isEmpty) {
        return 'Email is required';
      }

      if (!value.contains('@')) {
        return 'Enter a valid email';
      }

      return null;
    },
  ),
)




34. Restaurant API Integration


A backend API can provide restaurant information dynamically.


import 'dart:convert';
import 'package:http/http.dart' as http;

class RestaurantService {
  Future> fetchRestaurants() async {
    final response = await http.get(
      Uri.parse(
        'https://example.com/api/restaurants',
      ),
    );

    if (response.statusCode == 200) {
      final List data = jsonDecode(response.body);

      return data
          .map(
            (item) => Restaurant.fromJson(item),
          )
          .toList();
    }

    throw Exception(
      'Failed to load restaurants',
    );
  }
}


Flutter's networking documentation demonstrates using http.get(), checking the response status, decoding JSON, and converting the result into application-specific Dart objects. :contentReference[oaicite:5]{index=5}




35. Food API Integration


class FoodService {
  Future> fetchFoods() async {
    final response = await http.get(
      Uri.parse(
        'https://example.com/api/foods',
      ),
    );

    if (response.statusCode != 200) {
      throw Exception(
        'Failed to load food items',
      );
    }

    final List data = jsonDecode(response.body);

    return data
        .map(
          (item) => Food.fromJson(item),
        )
        .toList();
  }
}




36. Android Internet Permission


If the Android application communicates with an internet API, the application needs the Internet permission in its Android manifest.



This requirement is documented in Flutter's networking guide. :contentReference[oaicite:6]{index=6}




37. JSON Data Example


{
  "id": 1,
  "name": "Margherita Pizza",
  "description": "Classic pizza with tomato and cheese",
  "price": 299,
  "image": "https://example.com/pizza.jpg",
  "category": "Pizza",
  "rating": 4.5,
  "isVegetarian": true
}

JSON received from a server can be decoded with jsonDecode() and converted into Dart model objects. Flutter documents manual JSON decoding as a practical approach for smaller projects, while code generation can be useful as the number and complexity of models grows. :contentReference[oaicite:7]{index=7}




38. Loading State


Network requests can take time, so the application should display a loading indicator while restaurant or food data is being fetched.


if (isLoading) {
  return const Center(
    child: CircularProgressIndicator(),
  );
}



39. FutureBuilder


FutureBuilder can be used to display different UI depending on whether asynchronous data is loading, available, or has produced an error. :contentReference[oaicite:8]{index=8}


FutureBuilder>(
  future: foodService.fetchFoods(),
  builder: (context, snapshot) {
    if (snapshot.connectionState ==
        ConnectionState.waiting) {
      return const Center(
        child: CircularProgressIndicator(),
      );
    }

    if (snapshot.hasError) {
      return Center(
        child: Text(
          'Error: ${snapshot.error}',
        ),
      );
    }

    final foods = snapshot.data ?? [];

    return ListView.builder(
      itemCount: foods.length,
      itemBuilder: (context, index) {
        return FoodCard(
          food: foods[index],
        );
      },
    );
  },
)




40. Error Handling


Network operations can fail because of connectivity issues, server errors, invalid responses, authentication failures, or malformed data.


try {
  final foods = await foodService.fetchFoods();
} catch (error) {
  print('Failed to load food: $error');
}

A user-friendly application should display an understandable error message and provide a retry option where appropriate.




41. Retry Button


Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    const Text(
      'Unable to load restaurants',
    ),
    const SizedBox(height: 12),
    ElevatedButton(
      onPressed: () {
        loadRestaurants();
      },
      child: const Text('Retry'),
    ),
  ],
)



42. Empty Restaurant State


Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    const Icon(
      Icons.restaurant_outlined,
      size: 70,
    ),
    const SizedBox(height: 16),
    const Text(
      'No restaurants found',
      style: TextStyle(
        fontSize: 20,
        fontWeight: FontWeight.bold,
      ),
    ),
    const SizedBox(height: 8),
    const Text(
      'Try changing your search or location.',
    ),
  ],
)



43. Empty Cart State


Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    const Icon(
      Icons.shopping_bag_outlined,
      size: 80,
    ),
    const SizedBox(height: 16),
    const Text(
      'Your cart is empty',
      style: TextStyle(
        fontSize: 20,
        fontWeight: FontWeight.bold,
      ),
    ),
    const SizedBox(height: 12),
    ElevatedButton(
      onPressed: () {},
      child: const Text('Browse Food'),
    ),
  ],
)



44. Delivery Address Model


class Address {
  final String id;
  final String name;
  final String phone;
  final String addressLine;
  final String city;
  final String postalCode;

  Address({
    required this.id,
    required this.name,
    required this.phone,
    required this.addressLine,
    required this.city,
    required this.postalCode,
  });
}




45. Address Selection


Users can select an existing delivery address or add a new address before checkout.


ListTile(
  leading: const Icon(Icons.home),
  title: const Text('Home'),
  subtitle: const Text(
    '123 Main Street, Mumbai',
  ),
  trailing: Radio(
    value: 'home',
    groupValue: selectedAddress,
    onChanged: (value) {
      setState(() {
        selectedAddress = value;
      });
    },
  ),
)



46. Checkout Screen


The checkout screen brings together the selected food items, delivery address, charges, discounts, payment option, and final order amount.









Checkout ItemExample
Food Subtotal₹500
Delivery Fee₹40
Taxes₹30
Discount-₹50
Final Total₹520



47. Calculate Order Total


double calculateTotal({
  required double subtotal,
  required double deliveryFee,
  required double tax,
  required double discount,
}) {
  return subtotal +
      deliveryFee +
      tax -
      discount;
}



48. Order Model


class Order {
  final String id;
  final List items;
  final double total;
  final String status;
  final DateTime createdAt;
  final Address deliveryAddress;

  Order({
    required this.id,
    required this.items,
    required this.total,
    required this.status,
    required this.createdAt,
    required this.deliveryAddress,
  });
}




49. Order Status



  • Order Placed

  • Order Confirmed

  • Preparing Food

  • Food Ready

  • Picked Up

  • Out for Delivery

  • Delivered

  • Cancelled




50. Order Tracking


A simple order tracking interface can display the current stage of the order.


Order Placed
     ↓
Confirmed
     ↓
Preparing
     ↓
Picked Up
     ↓
Out for Delivery
     ↓
Delivered



51. Order Confirmation


Scaffold(
  appBar: AppBar(
    title: const Text('Order Confirmed'),
  ),
  body: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        const Icon(
          Icons.check_circle,
          size: 80,
        ),
        const SizedBox(height: 20),
        const Text(
          'Your order has been placed!',
          style: TextStyle(
            fontSize: 22,
            fontWeight: FontWeight.bold,
          ),
        ),
        const SizedBox(height: 10),
        Text(
          'Order ID: $orderId',
        ),
      ],
    ),
  ),
)



52. Order History


The order history screen displays previously placed orders and their current or final status.


ListView.builder(
  itemCount: orders.length,
  itemBuilder: (context, index) {
    final order = orders[index];

    return Card(
      child: ListTile(
        title: Text(
          'Order #${order.id}',
        ),
        subtitle: Text(
          order.status,
        ),
        trailing: Text(
          '₹${order.total.toStringAsFixed(2)}',
        ),
      ),
    );
  },
)




53. Payment Interface


A production food delivery application may support one or more payment methods such as cards, wallets, UPI, or cash on delivery, depending on the backend and payment provider.








Payment MethodExample
Cash on DeliveryPay when the order arrives.
CardPay using a debit or credit card.
UPIPay using a supported UPI application.
WalletPay using a supported digital wallet.

Sensitive payment operations should be handled through a trusted payment provider and backend rather than exposing confidential credentials in the Flutter client.




54. API Operations












OperationHTTP MethodPurpose
Get RestaurantsGETRetrieve restaurant information.
Get Food MenuGETRetrieve food items.
LoginPOSTAuthenticate the user.
RegisterPOSTCreate a new account.
Create OrderPOSTPlace a new food order.
Update ProfilePUT/PATCHUpdate user information.
Update OrderPUT/PATCHUpdate order status when permitted.
Delete AddressDELETERemove an address.



55. Sending an Order to an API


Future placeOrder(
  Map orderData,
) async {
  final response = await http.post(
    Uri.parse(
      'https://example.com/api/orders',
    ),
    headers: {
      'Content-Type': 'application/json',
    },
    body: jsonEncode(orderData),
  );

  if (response.statusCode != 201) {
    throw Exception(
      'Failed to place order',
    );
  }
}


The Flutter networking cookbook demonstrates sending data with HTTP methods such as POST and PUT and encoding request data as JSON. :contentReference[oaicite:9]{index=9}




56. Authentication Flow


Open App
    ↓
Check Login Status
    ↓
Not Logged In
    ↓
Login / Register
    ↓
Authentication Successful
    ↓
Home Screen
    ↓
Browse Restaurants
    ↓
Select Food
    ↓
Cart
    ↓
Checkout
    ↓
Place Order



57. Local Storage


Small pieces of local information can be stored on the device when required. For example, the application can store selected preferences or simple state information.


import 'package:shared_preferences/shared_preferences.dart';

Future saveSelectedAddress(
  String addressId,
) async {
  final prefs =
      await SharedPreferences.getInstance();

  await prefs.setString(
    'selected_address',
    addressId,
  );
}

Future getSelectedAddress() async {
  final prefs =
      await SharedPreferences.getInstance();

  return prefs.getString(
    'selected_address',
  );
}




58. Firebase Integration


Firebase can be used as a backend option for authentication, cloud database storage, file storage, analytics, notifications, and other services.


Flutter Application
        ↓
Firebase Authentication
        ↓
Cloud Firestore
        ↓
Firebase Storage

Flutter's data and backend documentation includes Firebase and Firestore among the available backend topics. :contentReference[oaicite:10]{index=10}




59. Responsive Restaurant Grid


A responsive layout can change the number of restaurant columns depending on the available screen width.


LayoutBuilder(
  builder: (context, constraints) {
    int columns = 1;

    if (constraints.maxWidth > 1000) {
      columns = 3;
    } else if (constraints.maxWidth > 600) {
      columns = 2;
    }

    return GridView.builder(
      gridDelegate:
          SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: columns,
        crossAxisSpacing: 12,
        mainAxisSpacing: 12,
        childAspectRatio: 0.75,
      ),
      itemCount: restaurants.length,
      itemBuilder: (context, index) {
        return RestaurantCard(
          restaurant: restaurants[index],
        );
      },
    );
  },
)




60. Bottom Navigation


A food delivery application can provide quick access to Home, Search, Orders, Favorites, and Profile.


BottomNavigationBar(
  currentIndex: selectedIndex,
  onTap: (index) {
    setState(() {
      selectedIndex = index;
    });
  },
  items: const [
    BottomNavigationBarItem(
      icon: Icon(Icons.home_outlined),
      label: 'Home',
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.search),
      label: 'Search',
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.receipt_long),
      label: 'Orders',
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.favorite_border),
      label: 'Favorites',
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.person_outline),
      label: 'Profile',
    ),
  ],
)



61. Notifications


A production food delivery application can notify customers about important order events such as confirmation, food preparation, pickup, delivery, or cancellation.



  • Order confirmation notification

  • Food preparation notification

  • Order pickup notification

  • Delivery notification

  • Promotional notification




62. API Loading Architecture


Flutter UI
    ↓
View / Screen
    ↓
ViewModel / Provider
    ↓
Service Class
    ↓
HTTP Request
    ↓
REST API
    ↓
JSON Response
    ↓
Dart Model
    ↓
Application State
    ↓
Flutter UI

Flutter's current learning materials describe MVVM as an architecture that separates the Model, View, and ViewModel responsibilities, helping make application code more reusable and testable. :contentReference[oaicite:11]{index=11}




63. MVVM Structure


Model
├── Food
├── Restaurant
├── CartItem
├── Address
└── Order

View
├── HomeScreen
├── RestaurantScreen
├── FoodDetailsScreen
├── CartScreen
└── CheckoutScreen

ViewModel
├── FoodViewModel
├── RestaurantViewModel
├── CartViewModel
└── OrderViewModel




64. Pagination


A food delivery application with a large restaurant or food catalog can use pagination so that only a limited number of records are loaded at a time.


Future> fetchRestaurants({
  int page = 1,
  int limit = 20,
}) async {
  final uri = Uri.parse(
    'https://example.com/api/restaurants'
    '?page=$page&limit=$limit',
  );

  final response = await http.get(uri);

  if (response.statusCode != 200) {
    throw Exception(
      'Unable to load restaurants',
    );
  }

  final data = jsonDecode(response.body) as List;

  return data
      .map(
        (item) => Restaurant.fromJson(item),
      )
      .toList();
}




65. Image Handling


Food applications rely heavily on images, so image loading should be handled carefully.


Image.network(
  food.image,
  width: double.infinity,
  height: 220,
  fit: BoxFit.cover,
  errorBuilder: (
    context,
    error,
    stackTrace,
  ) {
    return const Icon(
      Icons.image_not_supported,
      size: 60,
    );
  },
)



66. Performance Optimization



  • Use ListView.builder for long lists.

  • Use GridView.builder for large grids.

  • Optimize food and restaurant images.

  • Avoid unnecessary widget rebuilds.

  • Use appropriate state-management techniques.

  • Use pagination for large datasets.

  • Cache frequently used images when appropriate.

  • Keep networking code outside UI widgets.

  • Dispose controllers when they are no longer required.

  • Move expensive processing away from the main UI work when appropriate.




67. Security Considerations



  • Use HTTPS for production API communication.

  • Do not expose confidential API or payment credentials in the Flutter application.

  • Validate important order information on the backend.

  • Do not trust prices calculated only on the client.

  • Protect authentication tokens.

  • Validate user input.

  • Handle expired authentication sessions.

  • Protect customer and delivery information.




68. Testing the Food Delivery App









Testing TypeExample
Unit TestTest cart and order total calculations.
Widget TestTest whether food cards and buttons appear correctly.
Integration TestTest the restaurant-to-checkout flow.
API TestTest restaurant and food API responses.
Form TestTest login, registration, and address validation.



69. Common Problems and Solutions












ProblemPossible Solution
Restaurants are not loadingCheck the API URL, internet access, response status, and JSON structure.
Food images are brokenCheck image URLs and provide an error placeholder.
Cart becomes empty unexpectedlyKeep cart state in an appropriate shared state-management layer and persist it if required.
Wrong order totalCentralize price and quantity calculations and validate important totals on the backend.
Login failsCheck credentials, API response, authentication state, and token handling.
Checkout failsCheck address, cart contents, API response, and payment/order service.
Application is slowOptimize images, lists, state updates, and network operations.
API request fails on AndroidCheck Android Internet permission and network configuration.



70. Complete Application Architecture


                 FOOD DELIVERY APP
                         |
       +-----------------+-----------------+
       |                 |                 |
     Home              Orders           Profile
       |
 Search / Categories
       |
 Restaurants
       |
 Restaurant Details
       |
 Food Menu
       |
 Food Details
       |
 Add to Cart
       |
      Cart
       |
 Delivery Address
       |
    Checkout
       |
    Payment
       |
 Order Confirmation
       |
 Order Tracking
       |
 Order History



71. Suggested Development Steps



  1. Create the Flutter project.

  2. Configure the application theme.

  3. Create restaurant, food, category, cart, address, and order models.

  4. Design the splash and authentication screens.

  5. Build the home screen.

  6. Create food categories.

  7. Create restaurant cards.

  8. Build the restaurant details screen.

  9. Create food cards and food details.

  10. Connect the restaurant and food APIs.

  11. Add loading and error states.

  12. Create cart state management.

  13. Build the cart screen.

  14. Add quantity controls.

  15. Implement favorites.

  16. Add search and filters.

  17. Create login and registration.

  18. Build address management.

  19. Create the checkout screen.

  20. Implement order creation.

  21. Add order history.

  22. Add order tracking.

  23. Add local persistence where required.

  24. Test important application flows.

  25. Optimize performance.

  26. Prepare the application for release.




72. Best Practices



  • Use meaningful names for files, classes, variables, and methods.

  • Create reusable widgets.

  • Separate API code from UI code.

  • Use models for structured application data.

  • Keep cart calculations centralized.

  • Handle loading, empty, success, and error states.

  • Validate user forms.

  • Use responsive layouts.

  • Optimize images.

  • Use an appropriate state-management approach.

  • Keep sensitive backend operations secure.

  • Test the complete order flow.




73. Possible Future Enhancements



  • Live order tracking

  • GPS-based delivery location

  • Restaurant reviews

  • Food reviews and ratings

  • Coupon codes

  • Referral system

  • Multiple payment methods

  • Multiple delivery addresses

  • Favorite restaurants

  • Recommended food

  • Recently ordered food

  • Push notifications

  • Dark mode

  • Multi-language support

  • Restaurant admin panel

  • Delivery partner application

  • Restaurant order management

  • Analytics dashboard




74. Learning Outcomes


After completing this project, learners should understand how to build a practical food delivery application using Flutter. The project provides experience with Flutter widgets, Dart programming, models, API integration, JSON parsing, state management, navigation, authentication concepts, cart functionality, checkout, order management, local persistence, responsive design, error handling, and testing.




75. Interview Questions



  1. How would you structure a Flutter food delivery application?

  2. What is the purpose of a Food model?

  3. How do you convert JSON data into Dart objects?

  4. How do you fetch restaurant data from an API?

  5. Why are Future, async, and await used?

  6. How would you manage cart state across multiple screens?

  7. What is ChangeNotifier?

  8. How can Provider be used in a Flutter application?

  9. How would you implement food search?

  10. How would you filter vegetarian food?

  11. How would you calculate the final order amount?

  12. How would you implement order tracking?

  13. How would you handle API errors?

  14. How would you implement authentication?

  15. How would you make the application responsive?

  16. How would you optimize food images?

  17. What is pagination and why is it useful?

  18. How would you protect sensitive payment information?

  19. What is the difference between unit, widget, and integration testing?




76. Summary


Building a Flutter Food Delivery App is an excellent practical project for learning real-world mobile application development. The project combines restaurant discovery, food categories, menus, food details, search, filters, favorites, cart management, addresses, checkout, payments, authentication, API integration, order management, and responsive UI.


A beginner version can start with local food and restaurant data. As the project grows, API integration, authentication, shared state management, local persistence, Firebase, payment services, order tracking, and a structured architecture can be added.




Learn Flutter with JustAcademy


JustAcademy Flutter Training Course


Register for Flutter Course Demo


whatsapp