Introduction

Firestore and GetX together make one of the fastest and cleanest development stacks for Flutter – especially when building CRUD-based apps like

  • Student Management Systems

  • Task Manager Apps

  • Course Enrollment Apps

  • Inventory Systems

In this step-by-step guide, you will learn how to build a complete CRUD app in Flutter using:

โœ” Firebase Firestore (Cloud NoSQL Database)
โœ” GetX State Management
โœ” Firestore Collections & Data Relationships
โœ” Clean & Scalable Folder Structure

By the end, you will have a production-ready CRUD structure you can use in any real-world app.

Fix All 5 Common Flutter Firebase Setup Errors (Dart Not Recognized, Flutterfire Path, Project Naming & More)

๐ŸŸฆ Project Overview (Firestore Relationship Example)

We will build a Student & Class Management App, where:

  • Every class has many students (One-to-many relationship).

  • Each student belongs to one class (referenced by classId)

  • CRUD operations for both Classes and Students

Flutter Project Structure

๐Ÿ”ฅ Model Layer (Data Models)

1. Class Model
class ClassModel {
  String id;
  String name;

  ClassModel({required this.id, required this.name});

  Map<String, dynamic> toMap() => {"name": name};

  factory ClassModel.fromMap(Map<String, dynamic> data, String docId) {
    return ClassModel(id: docId, name: data["name"]);
  }
}
2. Student Model
class StudentModel {
  String id;
  String name;
  String classId;

  StudentModel({
    required this.id,
    required this.name,
    required this.classId,
  });

  Map<String, dynamic> toMap() => {
    "name": name,
    "classId": classId,
  };

  factory StudentModel.fromMap(Map<String, dynamic> data, String docId) {
    return StudentModel(
      id: docId,
      name: data["name"],
      classId: data["classId"],
    );
  }
}
๐Ÿ”ง Firestore Service Layer
import 'package:cloud_firestore/cloud_firestore.dart';

class FirestoreService {
  final classes = FirebaseFirestore.instance.collection("classes");
  final students = FirebaseFirestore.instance.collection("students");

  // CREATE
  Future<void> addClass(String name) =>
      classes.add({"name": name});

  Future<void> addStudent(String name, String classId) =>
      students.add({"name": name, "classId": classId});

  // UPDATE
  Future<void> updateClass(String id, String name) =>
      classes.doc(id).update({"name": name});

  Future<void> updateStudent(String id, String name) =>
      students.doc(id).update({"name": name});

  // DELETE
  Future<void> deleteClass(String id) =>
      classes.doc(id).delete();

  Future<void> deleteStudent(String id) =>
      students.doc(id).delete();
}
๐ŸŽฎ Controller Layer (GetX Logic)
1. Class Controller
import 'package:get/get.dart';
import '../models/class_model.dart';
import '../services/firestore_service.dart';

class ClassController extends GetxController {
  final service = FirestoreService();
  var classList = <ClassModel>[].obs;

  @override
  void onInit() {
    super.onInit();
    service.classes.snapshots().listen((snapshot) {
      classList.value = snapshot.docs
          .map((doc) => ClassModel.fromMap(doc.data(), doc.id))
          .toList();
    });
  }

  Future<void> addClass(String name) => service.addClass(name);
  Future<void> deleteClass(String id) => service.deleteClass(id);
}
2. Student Controller
import 'package:get/get.dart';
import '../models/student.dart';
import '../services/firestore_service.dart';

class StudentController extends GetxController {
  final service = FirestoreService();
  var studentList = <StudentModel>[].obs;

  @override
  void onInit() {
    super.onInit();
    service.students.snapshots().listen((snapshot) {
      studentList.value = snapshot.docs
          .map((doc) => StudentModel.fromMap(doc.data(), doc.id))
          .toList();
    });
  }

  Future<void> addStudent(String name, String classId) =>
      service.addStudent(name, classId);

  Future<void> deleteStudent(String id) =>
      service.deleteStudent(id);
}

Flutter Firebase Authentication Tutorial: Step-by-Step User Login & Signup Guide

๐ŸŽจ UI Layer (Simple & Clean UI)
Class List View
import 'package:firestore_databse_relationships/views/student_list_view.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';

import '../controllers/class_controller.dart';
import 'add_class_view.dart';

class ClassListView extends StatelessWidget {
  final controller = Get.put(ClassController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Classes")),
      floatingActionButton: FloatingActionButton(
        onPressed: () => Get.to(AddClassView()),
        child: Icon(Icons.add),
      ),
      body: Obx(() {
        return ListView(
          children: controller.classList.map((data) {
            return ListTile(
              title: Text(data.name),
              trailing: IconButton(
                icon: Icon(Icons.delete),
                onPressed: () => controller.deleteClass(data.id),
              ),
              onTap: () => Get.to(StudentListView(classId: data.id)),
            );
          }).toList(),
        );
      }),
    );
  }
}
Add Class View
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';

import '../controllers/class_controller.dart';

class AddClassView extends StatelessWidget {
  final controller = Get.find<ClassController>();
  final nameCtrl = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Add Class")),
      body: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          children: [
            TextField(controller: nameCtrl, decoration: InputDecoration(labelText: "Class Name")),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                controller.addClass(nameCtrl.text.trim());
                Get.back();
              },
              child: Text("Save"),
            )
          ],
        ),
      ),
    );
  }
}
Student List View
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import '../controllers/student_controller.dart';
import 'add_student_view.dart';

class StudentListView extends StatelessWidget {
  final String classId;
  StudentListView({required this.classId});

  final controller = Get.put(StudentController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Students")),
      floatingActionButton: FloatingActionButton(
        onPressed: () => Get.to(AddStudentView(classId: classId)),
        child: Icon(Icons.add),
      ),
      body: Obx(() {
        final students = controller.studentList
            .where((s) => s.classId == classId)
            .toList();

        return ListView(
          children: students.map((data) {
            return ListTile(
              title: Text(data.name),
              trailing: IconButton(
                icon: Icon(Icons.delete),
                onPressed: () => controller.deleteStudent(data.id),
              ),
            );
          }).toList(),
        );
      }),
    );
  }
}
Add Student View
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import '../controllers/student_controller.dart';

class AddStudentView extends StatelessWidget {
  final String classId;
  AddStudentView({required this.classId});

  final controller = Get.find<StudentController>();
  final nameCtrl = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Add Student")),
      body: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          children: [
            TextField(controller: nameCtrl, decoration: InputDecoration(labelText: "Student Name")),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                controller.addStudent(nameCtrl.text.trim(), classId);
                Get.back();
              },
              child: Text("Save"),
            )
          ],
        ),
      ),
    );
  }
}
Main.dart
// lib/main.dart

import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:get/get.dart';
import 'controllers/class_controller.dart';
import 'controllers/student_controller.dart';
import 'views/add_class_view.dart';
import 'views/add_student_view.dart';
import 'views/class_list_view.dart';
import 'views/student_list_view.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  // Register controllers globally so they live for the lifetime of the app.
  // You can also register them lazily with Bindings if you prefer.
  Get.put(ClassController(), permanent: true);
  Get.put(StudentController(), permanent: true);

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'Student Manager (GetX + Firestore)',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.deepPurple,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),

      // Start screen
      home: ClassListView(),

      // Optional: named routes for easier navigation (used with Get.toNamed)
      getPages: [
        GetPage(name: '/', page: () => ClassListView()),
        GetPage(name: '/add-class', page: () => AddClassView()),
        // StudentListView expects an argument classId; use Get.toNamed('/students', arguments: classId)
        GetPage(name: '/students', page: () {
          final classId = Get.arguments as String? ?? '';
          return StudentListView(classId: classId);
        }),
        GetPage(name: '/add-student', page: () {
          final classId = Get.arguments as String? ?? '';
          return AddStudentView(classId: classId);
        }),
      ],
    );
  }
}

๐Ÿš€ Conclusion

You have now built a fully functional Flutter Firestore CRUD App using GetX, including:

โœ” Clean architecture
โœ” Firestore service layer
โœ” GetX controllers
โœ” Real-time listeners
โœ” CRUD operations
โœ” Data relationships (Class โ†’ Students)
โœ” Beautiful and simple UI

This structure is production-ready and scalable for apps like:

  • School Management

  • Employee Management

  • Inventory System

  • Course Enrollment

  • Task Manager

๐Ÿ™ Thank You for Reading!

Thank you for taking the time to read this article. I truly appreciate your interest and support. I hope this guide added value to your learning journey and helped you understand Flutter, Firestore, and modern app development more clearly.

If you enjoy practical tech tutorials, coding guides, and real-world project walkthroughs, donโ€™t forget to subscribe to our blog. Your subscription helps us grow and encourages us to publish more high-quality tech content for developers like you.

Stay updated โ€” more exciting tutorials, tips, and advanced Flutter topics are coming soon! ๐Ÿš€๐Ÿ’ป

Author

Write A Comment