Flutter CRUD dengan SQLite
Tutorial Lengkap Membuat Aplikasi CRUD (Create, Read, Update, Delete) dengan Flutter dan Database SQLite
1. Setup Awal
Download dan Install Flutter SDK
Flutter SDK adalah framework untuk membuat aplikasi mobile cross-platform.
LANGKAH 1: Download Flutter SDK
1. Buka https://flutter.dev/docs/get-started/install
2. Pilih sistem operasi Anda (Windows/Mac/Linux)
3. Download file zip terbaru
4. Extract ke folder: C:\flutter (Windows) atau /Users/username/flutter (Mac)
LANGKAH 2: Setup Environment Variable (Windows)
1. Klik Windows Key + X → Edit environment variables
2. Klik "Environment Variables"
3. Buat variable baru:
- Nama: FLUTTER_HOME
- Nilai: C:\flutter
4. Edit variable Path, tambahkan: C:\flutter\bin
5. Klik OK → OK
LANGKAH 3: Verifikasi Instalasi
# Buka Command Prompt baru
flutter --version
flutter doctor
Install Android Studio atau VS Code
Pilih salah satu untuk development environment.
1. Download dari https://developer.android.com/studio
2. Install dan jalankan
3. Setup Android SDK saat pertama kali membuka
4. Buat Android Virtual Device (AVD) untuk emulator
1. Download dari https://code.visualstudio.com/
2. Install extensions:
- Flutter
- Dart
- Android Emulator (opsional)
2. Membuat Project Flutter
Buat Project Baru
Buka Command Prompt/Terminal di folder tempat Anda ingin menyimpan project:
flutter create student_crud
cd student_crud
Struktur Folder Project
Pahami struktur folder Flutter yang sudah dibuat:
student_crud/
├── lib/
│ ├── main.dart # Entry point aplikasi
│ ├── models/
│ │ └── student_model.dart # Data model
│ ├── database/
│ │ └── database_helper.dart # Database operations
│ ├── screens/
│ │ ├── home_screen.dart # Halaman utama
│ │ ├── add_student_screen.dart # Form tambah
│ │ └── edit_student_screen.dart # Form edit
│ └── widgets/
│ └── student_list_item.dart # Widget list item
├── pubspec.yaml # Dependencies
└── android/ios/web/ # Platform files
3. Database dan Dependencies
Konfigurasi pubspec.yaml
Tambahkan dependencies yang diperlukan untuk SQLite:
dependencies:
flutter:
sdk: flutter
sqflite: ^2.2.8+4
path: ^1.8.3
intl: ^0.19.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_linter:
sdk: flutter
Install dependencies:
flutter pub get
Buat Model Student
File: lib/models/student_model.dart
Model ini merepresentasikan data siswa dalam aplikasi:
class Student {
final int? id;
final String nama;
final String email;
final String nomorInduk;
final String? kelas;
final String? jurusan;
final String? alamat;
final String? nomorTelepon;
Student({
this.id,
required this.nama,
required this.email,
required this.nomorInduk,
this.kelas,
this.jurusan,
this.alamat,
this.nomorTelepon,
});
Map toMap() {
return {
'id': id,
'nama': nama,
'email': email,
'nomorInduk': nomorInduk,
'kelas': kelas,
'jurusan': jurusan,
'alamat': alamat,
'nomorTelepon': nomorTelepon,
};
}
factory Student.fromMap(Map map) {
return Student(
id: map['id'],
nama: map['nama'] ?? '',
email: map['email'] ?? '',
nomorInduk: map['nomorInduk'] ?? '',
kelas: map['kelas'],
jurusan: map['jurusan'],
alamat: map['alamat'],
nomorTelepon: map['nomorTelepon'],
);
}
}
Buat Database Helper
File: lib/database/database_helper.dart
Database Helper menangani semua operasi CRUD:
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import '../models/student_model.dart';
class DatabaseHelper {
static final DatabaseHelper _instance = DatabaseHelper._internal();
static Database? _database;
factory DatabaseHelper() {
return _instance;
}
DatabaseHelper._internal();
Future get database async {
if (_database != null) return _database!;
_database = await _initDatabase();
return _database!;
}
Future _initDatabase() async {
final databasesPath = await getDatabasesPath();
final path = join(databasesPath, 'student_crud.db');
return await openDatabase(
path,
version: 1,
onCreate: (db, version) {
return db.execute(
'CREATE TABLE students('
'id INTEGER PRIMARY KEY AUTOINCREMENT, '
'nama TEXT NOT NULL, '
'email TEXT UNIQUE NOT NULL, '
'nomorInduk TEXT UNIQUE NOT NULL, '
'kelas TEXT, '
'jurusan TEXT, '
'alamat TEXT, '
'nomorTelepon TEXT'
')',
);
},
);
}
// CREATE
Future insertStudent(Student student) async {
final db = await database;
return await db.insert('students', student.toMap());
}
// READ
Future> getAllStudents() async {
final db = await database;
final result = await db.query('students');
return result.map((map) => Student.fromMap(map)).toList();
}
Future getStudentById(int id) async {
final db = await database;
final result = await db.query(
'students',
where: 'id = ?',
whereArgs: [id],
);
if (result.isNotEmpty) {
return Student.fromMap(result.first);
}
return null;
}
// UPDATE
Future updateStudent(Student student) async {
final db = await database;
return await db.update(
'students',
student.toMap(),
where: 'id = ?',
whereArgs: [student.id],
);
}
// DELETE
Future deleteStudent(int id) async {
final db = await database;
return await db.delete(
'students',
where: 'id = ?',
whereArgs: [id],
);
}
// SEARCH
Future> searchStudents(String query) async {
final db = await database;
final result = await db.query(
'students',
where: 'nama LIKE ? OR email LIKE ? OR nomorInduk LIKE ?',
whereArgs: ['%$query%', '%$query%', '%$query%'],
);
return result.map((map) => Student.fromMap(map)).toList();
}
}
4. Membuat CRUD UI
Home Screen
Menampilkan daftar semua siswa dengan fitur search dan delete
Add Screen
Form untuk menambah siswa baru dengan validasi
Edit Screen
Form untuk mengubah data siswa yang sudah ada
List Widget
Komponen list item dengan action menu (edit/delete)
Main.dart - Entry Point
import 'package:flutter/material.dart';
import 'screens/home_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Student CRUD',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: const HomeScreen(),
debugShowCheckedModeBanner: false,
);
}
}
Home Screen - Daftar Siswa
File: lib/screens/home_screen.dart
Menampilkan list semua siswa dengan:
- Search functionality
- Delete dengan konfirmasi dialog
- Navigation ke Add/Edit screen
- Loading state dan empty state
import 'package:flutter/material.dart';
import '../database/database_helper.dart';
import '../models/student_model.dart';
import 'add_student_screen.dart';
import 'edit_student_screen.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
State createState() => _HomeScreenState();
}
class _HomeScreenState extends State {
final DatabaseHelper dbHelper = DatabaseHelper();
final TextEditingController searchController = TextEditingController();
List students = [];
List filteredStudents = [];
@override
void initState() {
super.initState();
loadStudents();
}
Future loadStudents() async {
final data = await dbHelper.getAllStudents();
setState(() {
students = data;
filteredStudents = data;
});
}
void searchStudents(String query) {
if (query.isEmpty) {
setState(() {
filteredStudents = students;
});
} else {
setState(() {
filteredStudents = students
.where((student) =>
student.nama.toLowerCase().contains(query.toLowerCase()) ||
student.email.toLowerCase().contains(query.toLowerCase()) ||
student.nomorInduk.contains(query))
.toList();
});
}
}
void deleteStudent(int id) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Hapus Siswa'),
content: const Text('Apakah Anda yakin ingin menghapus siswa ini?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Batal'),
),
TextButton(
onPressed: () async {
await dbHelper.deleteStudent(id);
Navigator.pop(context);
loadStudents();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Siswa berhasil dihapus')),
);
},
child: const Text('Hapus'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Daftar Siswa'),
centerTitle: true,
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
controller: searchController,
onChanged: searchStudents,
decoration: InputDecoration(
hintText: 'Cari siswa...',
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
),
Expanded(
child: filteredStudents.isEmpty
? const Center(
child: Text('Belum ada data siswa'),
)
: ListView.builder(
itemCount: filteredStudents.length,
itemBuilder: (context, index) {
final student = filteredStudents[index];
return Card(
margin: const EdgeInsets.all(8),
child: ListTile(
title: Text(student.nama),
subtitle: Text(student.email),
trailing: PopupMenuButton(
itemBuilder: (context) => [
PopupMenuItem(
child: const Text('Edit'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
EditStudentScreen(student: student),
),
).then((_) => loadStudents());
},
),
PopupMenuItem(
child: const Text('Hapus'),
onTap: () => deleteStudent(student.id!),
),
],
),
),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const AddStudentScreen(),
),
).then((_) => loadStudents());
},
child: const Icon(Icons.add),
),
);
}
}
Add Student Screen
File: lib/screens/add_student_screen.dart
import 'package:flutter/material.dart';
import '../database/database_helper.dart';
import '../models/student_model.dart';
class AddStudentScreen extends StatefulWidget {
const AddStudentScreen({Key? key}) : super(key: key);
@override
State createState() => _AddStudentScreenState();
}
class _AddStudentScreenState extends State {
final namaController = TextEditingController();
final emailController = TextEditingController();
final nomorIndukController = TextEditingController();
final kelasController = TextEditingController();
final jurusanController = TextEditingController();
final alamatController = TextEditingController();
final nomorTeleponController = TextEditingController();
final DatabaseHelper dbHelper = DatabaseHelper();
final _formKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Tambah Siswa'),
centerTitle: true,
),
body: Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(16),
children: [
TextFormField(
controller: namaController,
decoration: const InputDecoration(
labelText: 'Nama Siswa',
border: OutlineInputBorder(),
),
validator: (value) {
if (value!.isEmpty) {
return 'Nama tidak boleh kosong';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: emailController,
decoration: const InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
),
validator: (value) {
if (value!.isEmpty) {
return 'Email tidak boleh kosong';
}
if (!value.contains('@')) {
return 'Email tidak valid';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: nomorIndukController,
decoration: const InputDecoration(
labelText: 'Nomor Induk',
border: OutlineInputBorder(),
),
validator: (value) {
if (value!.isEmpty) {
return 'Nomor induk tidak boleh kosong';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: kelasController,
decoration: const InputDecoration(
labelText: 'Kelas',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
TextFormField(
controller: jurusanController,
decoration: const InputDecoration(
labelText: 'Jurusan',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
TextFormField(
controller: alamatController,
decoration: const InputDecoration(
labelText: 'Alamat',
border: OutlineInputBorder(),
),
maxLines: 3,
),
const SizedBox(height: 16),
TextFormField(
controller: nomorTeleponController,
decoration: const InputDecoration(
labelText: 'Nomor Telepon',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
final student = Student(
nama: namaController.text,
email: emailController.text,
nomorInduk: nomorIndukController.text,
kelas: kelasController.text,
jurusan: jurusanController.text,
alamat: alamatController.text,
nomorTelepon: nomorTeleponController.text,
);
await dbHelper.insertStudent(student);
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Siswa berhasil ditambahkan')),
);
}
},
child: const Padding(
padding: EdgeInsets.symmetric(vertical: 12),
child: Text('Simpan'),
),
),
],
),
),
);
}
}
Edit Student Screen
File: lib/screens/edit_student_screen.dart
import 'package:flutter/material.dart';
import '../database/database_helper.dart';
import '../models/student_model.dart';
class EditStudentScreen extends StatefulWidget {
final Student student;
const EditStudentScreen({Key? key, required this.student})
: super(key: key);
@override
State createState() => _EditStudentScreenState();
}
class _EditStudentScreenState extends State {
late TextEditingController namaController;
late TextEditingController emailController;
late TextEditingController nomorIndukController;
late TextEditingController kelasController;
late TextEditingController jurusanController;
late TextEditingController alamatController;
late TextEditingController nomorTeleponController;
final DatabaseHelper dbHelper = DatabaseHelper();
final _formKey = GlobalKey();
@override
void initState() {
super.initState();
namaController = TextEditingController(text: widget.student.nama);
emailController = TextEditingController(text: widget.student.email);
nomorIndukController =
TextEditingController(text: widget.student.nomorInduk);
kelasController =
TextEditingController(text: widget.student.kelas ?? '');
jurusanController =
TextEditingController(text: widget.student.jurusan ?? '');
alamatController =
TextEditingController(text: widget.student.alamat ?? '');
nomorTeleponController =
TextEditingController(text: widget.student.nomorTelepon ?? '');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Edit Siswa'),
centerTitle: true,
),
body: Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(16),
children: [
TextFormField(
controller: namaController,
decoration: const InputDecoration(
labelText: 'Nama Siswa',
border: OutlineInputBorder(),
),
validator: (value) {
if (value!.isEmpty) {
return 'Nama tidak boleh kosong';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: emailController,
decoration: const InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
),
validator: (value) {
if (value!.isEmpty) {
return 'Email tidak boleh kosong';
}
if (!value.contains('@')) {
return 'Email tidak valid';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: nomorIndukController,
decoration: const InputDecoration(
labelText: 'Nomor Induk',
border: OutlineInputBorder(),
),
validator: (value) {
if (value!.isEmpty) {
return 'Nomor induk tidak boleh kosong';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: kelasController,
decoration: const InputDecoration(
labelText: 'Kelas',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
TextFormField(
controller: jurusanController,
decoration: const InputDecoration(
labelText: 'Jurusan',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
TextFormField(
controller: alamatController,
decoration: const InputDecoration(
labelText: 'Alamat',
border: OutlineInputBorder(),
),
maxLines: 3,
),
const SizedBox(height: 16),
TextFormField(
controller: nomorTeleponController,
decoration: const InputDecoration(
labelText: 'Nomor Telepon',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
final student = Student(
id: widget.student.id,
nama: namaController.text,
email: emailController.text,
nomorInduk: nomorIndukController.text,
kelas: kelasController.text,
jurusan: jurusanController.text,
alamat: alamatController.text,
nomorTelepon: nomorTeleponController.text,
);
await dbHelper.updateStudent(student);
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Siswa berhasil diupdate')),
);
}
},
child: const Padding(
padding: EdgeInsets.symmetric(vertical: 12),
child: Text('Update'),
),
),
],
),
),
);
}
}
5. Testing dan Troubleshooting
Jalankan Aplikasi
# Jalankan di emulator atau device
flutter run
# Jalankan dengan verbose output (untuk debugging)
flutter run -v
# Release build untuk production
flutter build apk
Error dan Solusi Umum
Solusi:
1. Restart aplikasi
2. Bersihkan build folder:
flutter clean
flutter pub get
3. Run ulang aplikasi
Solusi:
1. Run: flutter pub get
2. Pastikan pubspec.yaml sudah ter-update
3. Run: flutter clean
4. Run: flutter pub get (ulangi)
5. Run: flutter run
Solusi:
1. Pastikan Android SDK sudah terinstall
2. Jalankan: flutter doctor
3. Ikuti instruksi dari output
4. Mulai ulang Android Emulator dari Android Studio
Testing Checklist
- Create: Tambah siswa baru dan lihat di list
- Read: Tampilkan semua siswa di halaman utama
- Update: Edit data siswa dan simpan perubahan
- Delete: Hapus siswa dan konfirmasi dialog
- Search: Cari siswa berdasarkan nama/email/nomor induk
- Validation: Coba submit form kosong (harus error)
- Duplicate: Coba tambah email/nomor induk yang sudah ada
Langkah Selanjutnya
Topik untuk pembelajaran lebih lanjut:
- State Management (Provider, Bloc)
- API Integration (HTTP, JSON)
- File Upload dan Image Picker
- Authentication dan Login
- Notifications dan Local Push
- Advanced UI/UX dengan Animations
- Testing (Unit Tests, Widget Tests)
- Deployment ke Google Play Store