flutter crud
Block Pattern

Flutter BLoC CRUD Tutorial with PHP MySQL REST API

📘 Flutter CRUD with BLoC and PHP/MySQL API


In modern mobile app development, efficient state management and seamless backend integration are essential for creating scalable and maintainable applications. This tutorial walks you through building a complete Student Management System using Flutter with the BLoC (Business Logic Component) pattern for state management and a PHP/MySQL API for backend operations. Whether you’re displaying a student list, adding new entries, editing records, or deleting students—this project covers the entire CRUD (Create, Read, Update, Delete) lifecycle in a clean and structured way.

By adopting the BLoC pattern, we ensure separation of concerns between UI and business logic, making the codebase modular and testable. On the backend, PHP scripts handle API requests while communicating with a MySQL database to perform data operations efficiently.

This hands-on project is ideal for Flutter developers looking to strengthen their understanding of reactive programming with BLoC, while also learning to connect Flutter apps to real-world backend services. Whether you’re building educational apps or internal enterprise tools, this setup offers a solid foundation for scalable cross-platform development.

✅ What You’ll Build

  • View a student list

  • Add a student

  • Edit student info

  • Delete a student
    All done cleanly with Flutter BLoC and powered by a PHP backend.

Project Structure:

lib/
│
├── main.dart
│
├── models/
│   └── student_model.dart
│
├── repositories/
│   └── student_repository.dart
│
├── blocs/
│   ├── student_bloc.dart
│   ├── student_event.dart
│   └── student_state.dart
│
├── pages/
│   └── student_page.dart

 


📦 API Endpoints:

Base URL: https://acesoftech.co.in/API/video/phpmysql-student-crud/

Action URL Method
View All view.php GET
Insert insert.php POST
Edit (get) edit.php GET
Update update.php POST
Delete delete.php POST

🛠️ Step-by-Step Full Tutorial


🔹 1. pubspec.yaml Dependencies

name: bloc_crud
description: A new Flutter project.
publish_to: 'none'
version: 1.0.0+1

environment:
  sdk: ^3.6.2

dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^8.1.3
  http: ^0.13.6
  equatable: ^2.0.5
  cupertino_icons: ^1.0.8

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^5.0.0

flutter:
  uses-material-design: true

 


🔹 2. Create Model – student_model.dart

class Student {
  final String id;
  final String firstName;
  final String lastName;
  final String email;
  Student({
    required this.id,
    required this.firstName,
    required this.lastName,
    required this.email,
  });
  factory Student.fromJson(Map<String, dynamic> json) {
    return Student(
      id: json['id'],
      firstName: json['first_name'],
      lastName: json['last_name'],
      email: json['email'],
    );
  }
  Map<String, String> toJson() => {
        'id': id,
        'first_name': firstName,
        'last_name': lastName,
        'email': email,
      };
}

 

 


🔹 3. Repository – student_repository.dart

import 'dart:convert';
import 'package:bloc_crud/models/student_model.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:http/http.dart' as http;

class StudentRepository {
  final String baseUrl =
      'https://acesoftech.co.in/API/video/phpmysql-student-crud';

  Future<List<Student>> fetchStudents() async {
    final response = await http.get(Uri.parse('$baseUrl/view.php'));
    if (kDebugMode) {
      print('Fetch Students Response: ${response.body}');
    }
    final List data = jsonDecode(response.body)['records'];
    return data.map((e) => Student.fromJson(e)).toList();
  }

  Future<void> addStudent(Student student) async {
    final response = await http.post(
      Uri.parse('$baseUrl/insert.php'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(student.toJson()),
    );
    if (kDebugMode) {
      print('Add Response: ${response.body}');
    }
  }

  Future<void> updateStudent(Student student) async {
    final response = await http.post(
      Uri.parse('$baseUrl/update.php'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(student.toJson()),
    );
    if (kDebugMode) {
      print('Update Response: ${response.body}');
    }
  }

  Future<void> deleteStudent(String id) async {
    final response = await http.get(Uri.parse('$baseUrl/delete.php?id=$id'));
    if (kDebugMode) {
      print('Delete Response: ${response.body}');
    }
  }

  Future<Student> getStudent(String id) async {
    final response = await http.get(Uri.parse('$baseUrl/edit.php?id=$id'));
    if (kDebugMode) {
      print('Get Student Response: ${response.body}');
    }
    return Student.fromJson(jsonDecode(response.body));
  }
}

 

 

 


🔹 4. Bloc Files

student_event.dart

import 'package:bloc_crud/models/student_model.dart';
import 'package:equatable/equatable.dart';

abstract class StudentEvent extends Equatable {
  const StudentEvent();

  @override
  List<Object> get props => [];
}

class LoadStudents extends StudentEvent {}

class AddStudent extends StudentEvent {
  final Student student;

  const AddStudent(this.student);

  @override
  List<Object> get props => [student];
}

class UpdateStudent extends StudentEvent {
  final Student student;

  const UpdateStudent(this.student);

  @override
  List<Object> get props => [student];
}

class DeleteStudent extends StudentEvent {
  final String id;

  const DeleteStudent(this.id);

  @override
  List<Object> get props => [id];
}

 


student_state.dart

import 'package:bloc_crud/models/student_model.dart';
import 'package:equatable/equatable.dart';

abstract class StudentState extends Equatable {
  @override
  List<Object?> get props => [];
}

class StudentInitial extends StudentState {}

class StudentLoading extends StudentState {}

class StudentLoaded extends StudentState {
  final List<Student> students;
  StudentLoaded(this.students);

  @override
  List<Object?> get props => [students];
}

class StudentAdded extends StudentState {}

class StudentUpdated extends StudentState {}

class StudentDeleted extends StudentState {}

class StudentError extends StudentState {
  final String message;
  StudentError(this.message);

  @override
  List<Object?> get props => [message];
}

 


student_bloc.dart

import 'package:bloc_crud/repositories/student_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'student_event.dart';
import 'student_state.dart';

class StudentBloc extends Bloc<StudentEvent, StudentState> {
  final StudentRepository repository;
  StudentBloc(this.repository) : super(StudentInitial()) {
    on<LoadStudents>(_onLoad);
    on<AddStudent>(_onAdd);
    on<UpdateStudent>(_onUpdate);
    on<DeleteStudent>(_onDelete);
  }
  void _onLoad(LoadStudents event, Emitter<StudentState> emit) async {
    emit(StudentLoading());
    try {
      final students = await repository.fetchStudents();
      emit(StudentLoaded(students));
    } catch (e) {
      emit(StudentError("Failed to load students"));
    }
  }
  void _onAdd(AddStudent event, Emitter<StudentState> emit) async {
    try {
      await repository.addStudent(event.student);
      emit(StudentAdded());
      add(LoadStudents());
    } catch (e) {
      emit(StudentError("Failed to add student"));
    }
  }
  void _onUpdate(UpdateStudent event, Emitter<StudentState> emit) async {
    try {
      await repository.updateStudent(event.student);
      emit(StudentUpdated());
      add(LoadStudents());
    } catch (e) {
      emit(StudentError("Failed to update student"));
    }
  }
  void _onDelete(DeleteStudent event, Emitter<StudentState> emit) async {
    try {
      await repository.deleteStudent(event.id);
      emit(StudentDeleted());
      add(LoadStudents());
    } catch (e) {
      emit(StudentError("Failed to delete student"));
    }
  }
}

 

 


🔹 5. UI – main.dart

import 'package:bloc_crud/blocs/student_bloc.dart';
import 'package:bloc_crud/blocs/student_event.dart';
import 'package:bloc_crud/pages/student_page.dart';
import 'package:bloc_crud/repositories/student_repository.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  final StudentRepository repo = StudentRepository();
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocProvider(
        create: (_) => StudentBloc(repo)..add(LoadStudents()),
        child: StudentPage(),
      ),
    );
  }
}

🔹 6. UI – student_page.dart

// student_page.dart
import 'package:bloc_crud/blocs/student_bloc.dart';
import 'package:bloc_crud/blocs/student_event.dart';
import 'package:bloc_crud/blocs/student_state.dart';
import 'package:bloc_crud/models/student_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class StudentPage extends StatelessWidget {
  final fnameController = TextEditingController();
  final lnameController = TextEditingController();
  final emailController = TextEditingController();

  StudentPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Student CRUD with BLoC')),
      body: BlocListener<StudentBloc, StudentState>(
        listener: (context, state) {
          if (state is StudentAdded) {
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(content: Text('Student added successfully')),
            );
          } else if (state is StudentUpdated) {
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(content: Text('Student updated successfully')),
            );
          } else if (state is StudentDeleted) {
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(content: Text('Student deleted successfully')),
            );
          } else if (state is StudentError) {
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('Error: ${state.message}')),
            );
          }
        },
        child: BlocBuilder<StudentBloc, StudentState>(
          builder: (context, state) {
            if (state is StudentLoading) {
              return const Center(child: CircularProgressIndicator());
            }

            if (state is StudentLoaded) {
              return ListView.builder(
                itemCount: state.students.length,
                itemBuilder: (context, index) {
                  final s = state.students[index];
                  return ListTile(
                    title: Text('${s.firstName} ${s.lastName}'),
                    subtitle: Text(s.email),
                    trailing: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        IconButton(
                          icon: const Icon(Icons.edit),
                          onPressed: () {
                            fnameController.text = s.firstName;
                            lnameController.text = s.lastName;
                            emailController.text = s.email;

                            showDialog(
                              context: context,
                              builder: (_) => AlertDialog(
                                title: const Text('Edit Student'),
                                content: Column(
                                  mainAxisSize: MainAxisSize.min,
                                  children: [
                                    TextField(
                                      controller: fnameController,
                                      decoration: const InputDecoration(
                                        hintText: 'First Name',
                                      ),
                                    ),
                                    TextField(
                                      controller: lnameController,
                                      decoration: const InputDecoration(
                                        hintText: 'Last Name',
                                      ),
                                    ),
                                    TextField(
                                      controller: emailController,
                                      decoration: const InputDecoration(
                                        hintText: 'Email Address',
                                      ),
                                    ),
                                  ],
                                ),
                                actions: [
                                  ElevatedButton(
                                    onPressed: () {
                                      final updated = Student(
                                        id: s.id,
                                        firstName: fnameController.text,
                                        lastName: lnameController.text,
                                        email: emailController.text,
                                      );
                                      context
                                          .read<StudentBloc>()
                                          .add(UpdateStudent(updated));
                                      Navigator.pop(context);
                                    },
                                    child: const Text('Save'),
                                  ),
                                ],
                              ),
                            );
                          },
                        ),
                        IconButton(
                          icon: const Icon(Icons.delete),
                          onPressed: () {
                            context
                                .read<StudentBloc>()
                                .add(DeleteStudent(s.id));
                          },
                        ),
                      ],
                    ),
                  );
                },
              );
            }

            return const Center(child: Text('No students found'));
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          fnameController.clear();
          lnameController.clear();
          emailController.clear();

          showDialog(
            context: context,
            builder: (_) => AlertDialog(
              title: const Text('Add Student'),
              content: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  TextField(
                    controller: fnameController,
                    decoration: const InputDecoration(
                      hintText: 'First Name',
                    ),
                  ),
                  TextField(
                    controller: lnameController,
                    decoration: const InputDecoration(
                      hintText: 'Last Name',
                    ),
                  ),
                  TextField(
                    controller: emailController,
                    decoration: const InputDecoration(
                      hintText: 'Email Address',
                    ),
                  ),
                ],
              ),
              actions: [
                ElevatedButton(
                  onPressed: () {
                    final newStudent = Student(
                      id: '', // Backend should auto-generate the ID
                      firstName: fnameController.text,
                      lastName: lnameController.text,
                      email: emailController.text,
                    );
                    context.read<StudentBloc>().add(AddStudent(newStudent));
                    Navigator.pop(context);
                  },
                  child: const Text('Add'),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}

 

Leave a Reply

Your email address will not be published. Required fields are marked *