Implement API

Check this

https://testsvfcb.pythonanywhere.com

1. Setup Retrofit for Flutter

Retrofit simplifies API handling by auto-generating code based on annotations.

Step 1: Add Dependencies

Update your pubspec.yaml with the following:

dependencies:
  dio: ^5.0.0
  retrofit: ^4.0.0
  json_annotation: ^4.8.0

dev_dependencies:
  retrofit_generator: ^7.0.0
  build_runner: ^2.4.4

Run:

flutter pub get

File Structure

features/
└── restaurant_crud/
    ├── components/
    │   ├── restaurant_list_component.dart
    │   ├── restaurant_form_component.dart
    │   └── restaurant_card_component.dart
    └── screen/
        ├── restaurant_add_page.dart
        ├── restaurant_all_page.dart
        ├── restaurant_edit_page.dart
        └── restaurant_single_page.dart
core/
└── models/
    └── restaurant_model.dart
└── services/
    └── restaurant_api_service.dart
main.dart

Step 1: Create the Restaurant Model

File: restaurant_model.dart

class Restaurant {
  final String id;
  final String name;
  final String address;
  final String phone;
  final String email;
  final bool status;
  final String description;
  final int userId;
  final int categoryId;

  Restaurant({
    required this.id,
    required this.name,
    required this.address,
    required this.phone,
    required this.email,
    required this.status,
    required this.description,
    required this.userId,
    required this.categoryId,
  });

  // Factory method to convert JSON to object
  factory Restaurant.fromJson(Map<String, dynamic> json) {
    return Restaurant(
      id: json['restaurant_uuid'] ?? '',
      name: json['name'] ?? '',
      address: json['address'] ?? '',
      phone: json['phone'] ?? '',
      email: json['email'] ?? '',
      status: json['status'] ?? false,
      description: json['description'] ?? '',
      userId: json['user_id'] ?? 0,
      categoryId: json['category_id'] ?? 0,
    );
  }

  // Convert object to JSON for POST/PUT requests
  Map<String, dynamic> toJson() {
    return {
      'name': name,
      'address': address,
      'phone': phone,
      'email': email,
      'status': status,
      'description': description,
      'user_id': userId,
      'category_id': categoryId,
    };
  }
}

Step 2: Retrofit API Service

File: restaurant_api_service.dart

Using Retrofit, the following interface defines the CRUD API endpoints.

import 'package:dio/dio.dart';
import 'package:retrofit/retrofit.dart';
import '../models/restaurant_model.dart';

part 'restaurant_api_service.g.dart';

@RestApi(baseUrl: "https://testsvfcb.pythonanywhere.com")
abstract class RestaurantApiService {
  factory RestaurantApiService(Dio dio, {String baseUrl}) = _RestaurantApiService;

  @GET("/restaurant/")
  Future<List<Restaurant>> getAllRestaurants();

  @GET("/restaurant/{id}")
  Future<Restaurant> getRestaurantById(@Path("id") String id);

  @POST("/restaurant/")
  Future<void> createRestaurant(@Body() Restaurant restaurant);

  @PUT("/restaurant/{id}")
  Future<void> updateRestaurant(@Path("id") String id, @Body() Restaurant restaurant);

  @DELETE("/restaurant/{id}")
  Future<void> deleteRestaurant(@Path("id") String id);
}

Step 3: Generate Retrofit Code

Run the following command to generate the required API implementation:

flutter pub run build_runner build

Step 4: Components

1. Restaurant List Component

File: restaurant_list_component.dart

import 'package:flutter/material.dart';
import '../../../core/models/restaurant_model.dart';
import '../../../core/services/restaurant_api_service.dart';
import 'restaurant_card_component.dart';

class RestaurantListComponent extends StatefulWidget {
  const RestaurantListComponent({Key? key}) : super(key: key);

  @override
  State<RestaurantListComponent> createState() => _RestaurantListComponentState();
}

class _RestaurantListComponentState extends State<RestaurantListComponent> {
  late final RestaurantApiService apiService;
  List<Restaurant> restaurants = [];

  @override
  void initState() {
    super.initState();
    apiService = RestaurantApiService(Dio());
    fetchRestaurants();
  }

  void fetchRestaurants() async {
    final data = await apiService.getAllRestaurants();
    setState(() {
      restaurants = data;
    });
  }

  void deleteRestaurant(String id) async {
    await apiService.deleteRestaurant(id);
    fetchRestaurants();
  }

  @override
  Widget build(BuildContext context) {
    return Wrap(
      spacing: 10,
      runSpacing: 10,
      children: restaurants
          .map((restaurant) => RestaurantCardComponent(
                restaurant: restaurant,
                onDelete: () => deleteRestaurant(restaurant.id),
              ))
          .toList(),
    );
  }
}

2. Restaurant Card Component

File: restaurant_card_component.dart

import 'package:flutter/material.dart';
import '../../../core/models/restaurant_model.dart';

class RestaurantCardComponent extends StatelessWidget {
  final Restaurant restaurant;
  final VoidCallback onDelete;

  const RestaurantCardComponent({
    Key? key,
    required this.restaurant,
    required this.onDelete,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 4,
      child: Padding(
        padding: const EdgeInsets.all(12.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text("Name: ${restaurant.name}",
                style: const TextStyle(fontWeight: FontWeight.bold)),
            Text("Address: ${restaurant.address}"),
            Text("Phone: ${restaurant.phone}"),
            Row(
              mainAxisAlignment: MainAxisAlignment.end,
              children: [
                IconButton(
                  icon: const Icon(Icons.edit, color: Colors.blue),
                  onPressed: () {
                    // Navigate to Edit Page
                  },
                ),
                IconButton(
                  icon: const Icon(Icons.delete, color: Colors.red),
                  onPressed: onDelete,
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

3. Restaurant Add Page

File: restaurant_add_page.dart

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

import '../../../core/model/restaurant_model.dart';
import '../../../core/services/restaurant_api_service.dart';

class RestaurantAddPage extends StatelessWidget {
  final RestaurantApiService apiService = RestaurantApiService(Dio());
  final TextEditingController nameController = TextEditingController();
  final TextEditingController addressController = TextEditingController();
  final FocusNode nameFocus = FocusNode();
  final FocusNode addressFocus = FocusNode();

  RestaurantAddPage({Key? key}) : super(key: key);

  void saveRestaurant(BuildContext context) async {
    nameFocus.unfocus();
    addressFocus.unfocus();

    final newRestaurant = Restaurant(
      id: '',
      name: nameController.text,
      address: addressController.text,
      phone: "09517477717",
      email: "test@test.com",
      status: true,
      description: "This is restaurant",
      userId: 1,
      categoryId: 2,
    );

    try {
      await apiService.createRestaurant(newRestaurant);
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text("Restaurant saved successfully!")),
      );
      context.go('/restaurant'); 

    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text("Error saving restaurant: $e")),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Add Restaurant")),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: nameController,
              focusNode: nameFocus,
              decoration: const InputDecoration(labelText: "Name"),
            ),
            TextField(
              controller: addressController,
              focusNode: addressFocus,
              decoration: const InputDecoration(labelText: "Address"),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () => saveRestaurant(context),
              child: const Text("Save"),
            ),
          ],
        ),
      ),
    );
  }
}

  1. Restaurant All Page
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

import '../../../core/model/restaurant_model.dart';
import '../../../core/services/restaurant_api_service.dart';
import '../components/restaurant_list.dart';

class RestaurantAllPage extends StatefulWidget {
  const RestaurantAllPage({Key? key}) : super(key: key);

  @override
  _RestaurantAllPageState createState() => _RestaurantAllPageState();
}

class _RestaurantAllPageState extends State<RestaurantAllPage> {
  late final RestaurantApiService apiService;
  List<Restaurant> restaurants = [];

  @override
  void initState() {
    super.initState();
    apiService = RestaurantApiService(Dio());
    fetchRestaurants();
  }

  void fetchRestaurants() async {
    try {
      final data = await apiService.getAllRestaurants();
      setState(() {
        restaurants = data;
      });
    } catch (e) {
      print("Error fetching restaurants: $e");
    }
  }

  void deleteRestaurant(String id) async {
    await apiService.deleteRestaurant(id);
    fetchRestaurants();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("All Restaurants"),
      ),
      body:
      ListView.builder(
        itemCount: restaurants.length,
        itemBuilder: (context, index) {
          final restaurant = restaurants[index];
          return Card(
            margin: const EdgeInsets.symmetric(vertical: 8),
            child: ListTile(
              title: Text(restaurant.name),
              subtitle: Text(restaurant.address),
              trailing: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  IconButton(
                    icon: const Icon(Icons.edit, color: Colors.blue),
                    onPressed: () {
                      context.go('/edit-restaurant/${restaurant.id}');
                    },
                  ),
                  IconButton(
                    icon: const Icon(Icons.delete, color: Colors.red),
                    onPressed: () {
                      deleteRestaurant(restaurant.id);
                    },
                  ),
                ],
              ),
            ),
          );
        },
      ),

      floatingActionButton: FloatingActionButton(
        onPressed: () {
          context.go('/add-restaurant'); // Navigate to Add Page
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

Updated on