AI Reading
Quick summary of this article
This tutorial walks through building a Flutter Student Marksheet App using the BLoC pattern for state management and Hive for offline data storage. The app lets users add, update, and delete student records with subject marks, calculates totals, percentages, and pass/fail status, and persists data across app restarts. It demonstrates a clean, structured approach with separate models, BLoC logic, and UI layers.
- Core features: Manage student records, calculate total, percentage, grade, and pass/fail status; all data is stored offline using Hive.
- Tech stack: Uses Flutter, the BLoC pattern for state management, and Hive (with hive_flutter) for local storage.
- Project structure: Organized into separate folders for BLoC (events, states, logic), models, and screens, following clean architecture principles.
- Key implementation steps: Initialize Hive in main.dart, define a Student model with Hive annotations, create BLoC events and states, and build a UI with a list view and input form.
- User feedback: The app shows success messages (e.g., "Student added successfully") by comparing previous and current state lengths after add or delete actions.
Building a Flutter Student Marksheet App with Hive and BLoC (Full Tutorial)
Introduction
In today’s tutorial, we’ll build a complete Student Marksheet App in Flutter using the BLoC pattern for state management and Hive for offline data storage. This app will allow users to:
- Add students and their subject marks
- Update existing marks
- Delete students
- Persist all data even after the app restarts
By the end of this tutorial, you’ll have a powerful offline-capable Flutter app that uses clean architecture and best practices.
Let’s dive in!
Project Overview
Main Features:
- Manage student records
- Calculate total, percentage, and pass/fail status
- Offline storage with Hive
- State management with BLoC
Project Structure:
/lib
/bloc
student_bloc.dart
student_event.dart
student_state.dart
/models
student.dart
/screens
student_page.dart
main.dart
Step 1: Setup the Flutter Project
First, create a new Flutter project:
flutter create student_marksheet
Navigate into the project directory:
cd student_marksheet
Step 2: Add Dependencies
Open your pubspec.yaml file and add these packages:
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
flutter_bloc: ^9.1.1
hive: ^2.2.3
hive_flutter: ^1.1.0
Then install them:
flutter pub get
Step 3: Initialize Hive in main.dart
We need to initialize Hive and open a box before using it.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:student_marksheet/bloc/student_event.dart';
import 'bloc/student_bloc.dart';
import 'screens/student_page.dart';
import 'models/student.dart';
void main() async {
await Hive.initFlutter();
Hive.registerAdapter(StudentAdapter());
await Hive.openBox<Student>('students');
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Student Marksheet',
home: BlocProvider(
create: (context) => StudentBloc()..add(LoadStudentsEvent()),
child: StudentPage(),
),
);
}
}
Step 4: Create the Student Model
Let’s define a simple model for our students.
// lib/models/student.dart
import 'package:hive/hive.dart';
part 'student.g.dart';
@HiveType(typeId: 0)
class Student extends HiveObject {
@HiveField(0)
String name;
@HiveField(1)
int mark1;
@HiveField(2)
int mark2;
@HiveField(3)
int mark3;
Student({required this.name, required this.mark1, required this.mark2, required this.mark3});
int get total => mark1 + mark2 + mark3;
double get percentage => total / 3;
String get grade {
if (percentage >= 80) return 'A';
if (percentage >= 60) return 'B';
if (percentage >= 40) return 'C';
return 'F';
}
bool get isPass => percentage >= 40;
}
student.g.dart
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'student.dart';
class StudentAdapter extends TypeAdapter<Student> {
@override
final int typeId = 0;
@override
Student read(BinaryReader reader) {
return Student(
name: reader.readString(),
mark1: reader.readInt(),
mark2: reader.readInt(),
mark3: reader.readInt(),
);
}
@override
void write(BinaryWriter writer, Student obj) {
writer.writeString(obj.name);
writer.writeInt(obj.mark1);
writer.writeInt(obj.mark2);
writer.writeInt(obj.mark3);
}
}
Simple and flexible: students have a name and a map of subjects to marks.
Step 5: Create the BLoC Layer
5.1 Events
Define the possible actions in student_event.dart:
abstract class StudentEvent {}
class LoadStudentsEvent extends StudentEvent {}
class AddStudentEvent extends StudentEvent {
final String name;
final int mark1, mark2, mark3;
AddStudentEvent(this.name, this.mark1, this.mark2, this.mark3);
}
class DeleteStudentEvent extends StudentEvent {
final int index;
DeleteStudentEvent(this.index);
}
5.2 States
Define states in student_state.dart:
import '../models/student.dart';
abstract class StudentState {}
class StudentInitial extends StudentState {}
class StudentLoaded extends StudentState {
final List<Student> students;
StudentLoaded(this.students);
}
5.3 BLoC Logic
Now the main logic in student_bloc.dart:
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hive/hive.dart';
import '../models/student.dart';
import 'student_event.dart';
import 'student_state.dart';
class StudentBloc extends Bloc<StudentEvent, StudentState> {
final Box<Student> studentBox = Hive.box<Student>('students');
StudentState? previousState; // Store previous state to detect changes
StudentBloc() : super(StudentInitial()) {
on<LoadStudentsEvent>((event, emit) {
emit(StudentLoaded(studentBox.values.toList()));
});
on<AddStudentEvent>((event, emit) async {
// Track previous state before adding a student
previousState = state;
final newStudent = Student(
name: event.name,
mark1: event.mark1,
mark2: event.mark2,
mark3: event.mark3,
);
await studentBox.add(newStudent);
// Emit new state after adding student
emit(StudentLoaded(studentBox.values.toList()));
});
on<DeleteStudentEvent>((event, emit) async {
// Track previous state before deleting a student
previousState = state;
await studentBox.deleteAt(event.index);
// Emit new state after deleting student
emit(StudentLoaded(studentBox.values.toList()));
});
}
// Helper method to detect changes and show success messages
String getSuccessMessage(StudentState currentState) {
if (previousState is StudentLoaded && currentState is StudentLoaded) {
final prevLength = (previousState as StudentLoaded).students.length;
final currentLength = currentState.students.length;
if (currentLength > prevLength) {
return 'Student added successfully';
} else if (currentLength < prevLength) {
return 'Student deleted successfully';
}
}
return '';
}
}
This BLoC handles adding, updating, deleting, saving to Hive, and loading from Hive!
Step 6: Build the UI
Create a UI to interact with students.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../bloc/student_bloc.dart';
import '../bloc/student_event.dart';
import '../bloc/student_state.dart';
import '../models/student.dart';
class StudentPage extends StatelessWidget {
final nameController = TextEditingController();
final mark1Controller = TextEditingController();
final mark2Controller = TextEditingController();
final mark3Controller = TextEditingController();
void _showMessage(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Student Marksheet')),
body: BlocListener<StudentBloc, StudentState>(
listenWhen: (previous, current) =>
previous is StudentLoaded && current is StudentLoaded,
listener: (context, state) {
if (state is StudentLoaded) {
final prevState = context.read<StudentBloc>().previousState;
if (prevState is StudentLoaded) {
final prevLength = prevState.students.length;
final currentLength = state.students.length;
if (currentLength > prevLength) {
_showMessage(context, 'Student added successfully');
} else if (currentLength < prevLength) {
_showMessage(context, 'Student deleted successfully');
}
}
}
},
child: BlocBuilder<StudentBloc, StudentState>(
builder: (context, state) {
if (state is StudentLoaded) {
return Column(
children: [
Expanded(
child: ListView.builder(
itemCount: state.students.length,
itemBuilder: (context, index) {
final student = state.students[index];
return ListTile(
title: Text(student.name),
subtitle: Text('Total: ${student.total}, Grade: ${student.grade}, ${student.isPass ? 'Pass' : 'Fail'}'),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
BlocProvider.of<StudentBloc>(context).add(DeleteStudentEvent(index));
},
),
);
},
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
TextField(controller: nameController, decoration: InputDecoration(labelText: 'Name')),
TextField(controller: mark1Controller, decoration: InputDecoration(labelText: 'Mark 1'), keyboardType: TextInputType.number),
TextField(controller: mark2Controller, decoration: InputDecoration(labelText: 'Mark 2'), keyboardType: TextInputType.number),
TextField(controller: mark3Controller, decoration: InputDecoration(labelText: 'Mark 3'), keyboardType: TextInputType.number),
ElevatedButton(
onPressed: () {
final name = nameController.text.trim();
final mark1 = int.tryParse(mark1Controller.text.trim());
final mark2 = int.tryParse(mark2Controller.text.trim());
final mark3 = int.tryParse(mark3Controller.text.trim());
if (name.isEmpty) {
_showMessage(context, 'Please enter a name');
return;
}
if (mark1 == null || mark2 == null || mark3 == null) {
_showMessage(context, 'Please enter valid numeric marks');
return;
}
BlocProvider.of<StudentBloc>(context).add(
AddStudentEvent(name, mark1, mark2, mark3),
);
nameController.clear();
mark1Controller.clear();
mark2Controller.clear();
mark3Controller.clear();
},
child: Text('Add Student'),
),
],
),
),
],
);
}
return Center(child: CircularProgressIndicator());
},
),
),
);
}
}
Conclusion
Congratulations! 🎉
You have now built a complete Student Marksheet App in Flutter using Hive and BLoC. The app is:
- Fully offline capable ✅
- Using clean BLoC pattern ✅
- Easy to extend ✅
