Flutter CRUD dengan SQLite

Tutorial Lengkap Membuat Aplikasi CRUD (Create, Read, Update, Delete) dengan Flutter dan Database SQLite

1. Setup Awal

1

Download dan Install Flutter SDK

Flutter SDK adalah framework untuk membuat aplikasi mobile cross-platform.

Langkah Instalasi
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
Jika muncul versi Flutter, instalasi berhasil!
2

Install Android Studio atau VS Code

Pilih salah satu untuk development environment.

Opsi A: Android Studio (Recommended)
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
Opsi B: VS Code (Lightweight)
1. Download dari https://code.visualstudio.com/
2. Install extensions:
   - Flutter
   - Dart
   - Android Emulator (opsional)

2. Membuat Project Flutter

1

Buat Project Baru

Buka Command Prompt/Terminal di folder tempat Anda ingin menyimpan project:

Command Prompt
flutter create student_crud
cd student_crud
Proses pembuatan project memakan waktu beberapa menit
2

Struktur Folder Project

Pahami struktur folder Flutter yang sudah dibuat:

Folder Structure
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

1

Konfigurasi pubspec.yaml

Tambahkan dependencies yang diperlukan untuk SQLite:

pubspec.yaml
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:

Command Prompt
flutter pub get
2

Buat Model Student

File: lib/models/student_model.dart

Model ini merepresentasikan data siswa dalam aplikasi:

student_model.dart
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'],
    );
  }
}
3

Buat Database Helper

File: lib/database/database_helper.dart

Database Helper menangani semua operasi CRUD:

database_helper.dart
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)

1

Main.dart - Entry Point

lib/main.dart
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,
    );
  }
}
2

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
home_screen.dart
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),
      ),
    );
  }
}
3

Add Student Screen

File: lib/screens/add_student_screen.dart

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'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
4

Edit Student Screen

File: lib/screens/edit_student_screen.dart

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

1

Jalankan Aplikasi

Command Prompt
# Jalankan di emulator atau device
flutter run

# Jalankan dengan verbose output (untuk debugging)
flutter run -v

# Release build untuk production
flutter build apk
2

Error dan Solusi Umum

Error: Database locked
Solusi:
1. Restart aplikasi
2. Bersihkan build folder:
   flutter clean
   flutter pub get
3. Run ulang aplikasi
Error: sqflite not found
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
Emulator tidak terdeteksi
Solusi:
1. Pastikan Android SDK sudah terinstall
2. Jalankan: flutter doctor
3. Ikuti instruksi dari output
4. Mulai ulang Android Emulator dari Android Studio
3

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
4

Langkah Selanjutnya

Topik untuk pembelajaran lebih lanjut:

  1. State Management (Provider, Bloc)
  2. API Integration (HTTP, JSON)
  3. File Upload dan Image Picker
  4. Authentication dan Login
  5. Notifications dan Local Push
  6. Advanced UI/UX dengan Animations
  7. Testing (Unit Tests, Widget Tests)
  8. Deployment ke Google Play Store