MEMBUAT SISTEM CRUID DI FLUTTER DAN FIREBASE
01. 🗂️ Membuat file dan folder terstruktur dan rapi.
01.1. 🧹 MEMANAJEMEN FOLDER & FILE SUPAYA RAPI & TERSTRUKTUR
📂lib/
├─🎯main.dart // File utama aplikasi
├─📂views/ // Folder untuk tampilan (UI)
│ ├─📋mahasiswa_list.dart // Daftar mahasiswa
│ ├─➕mahasiswa_add.dart // Menambah mahasiswa
│ ├─👀mahasiswa_detail.dart // Detail mahasiswa
│ ├─✏️mahasiswa_edit.dart // Edit mahasiswa
├─📂models/ // Folder untuk model data
│ ├─📦mahasiswa_model.dart // Struktur data mahasiswa
├─📂services/ // Folder untuk logika bisnis
│ ├─🛠️mahasiswa_service.dart // Layanan Firestore
02. NGODING TIPIS-TIPIS DI 🏁
/lib/main.dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'views/mahasiswa_list.dart';
import 'firebase_options.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'CRUD Mahasiswa',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MahasiswaListView(),
);
}
}
03. NGODING TIPIS-TIPIS DI 🛠️
/lib/services/mahasiswa_service.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import '../models/mahasiswa_model.dart';
class MahasiswaService {
final CollectionReference mahasiswaCollection =
FirebaseFirestore.instance.collection('mahasiswa');
// Fetch all mahasiswa
Future<List<Mahasiswa>> fetchMahasiswa() async {
QuerySnapshot snapshot = await mahasiswaCollection.get();
return snapshot.docs
.map((doc) => Mahasiswa.fromFirestore(
doc.data() as Map<String, dynamic>,
doc.id,
))
.toList();
}
// Add new mahasiswa
Future<void> addMahasiswa(Mahasiswa mahasiswa) async {
await mahasiswaCollection.add(mahasiswa.toMap());
}
// Update mahasiswa
Future<void> updateMahasiswa(String id, Mahasiswa mahasiswa) async {
await mahasiswaCollection.doc(id).update(mahasiswa.toMap());
}
// Delete mahasiswa
Future<void> deleteMahasiswa(String id) async {
await mahasiswaCollection.doc(id).delete();
}
}
04. NGODING TIPIS-TIPIS DI 📦
/lib/models/mahasiswa_model.dart
class Mahasiswa {
final String id;
final String nama;
final String nim;
final String prodi;
final String jurusan;
Mahasiswa({
required this.id,
required this.nama,
required this.nim,
required this.prodi,
required this.jurusan,
});
// Convert Firestore document to Mahasiswa object
factory Mahasiswa.fromFirestore(Map<String, dynamic> data, String id) {
return Mahasiswa(
id: id,
nama: data['nama'] ?? '',
nim: data['nim'] ?? '',
prodi: data['prodi'] ?? '',
jurusan: data['jurusan'] ?? '',
);
}
// Convert Mahasiswa object to Firestore document
Map<String, dynamic> toMap() {
return {
'nama': nama,
'nim': nim,
'prodi': prodi,
'jurusan': jurusan,
};
}
}
05.1.NAH SEKARANG MASUK KE VIEW 👁️👁️
05.2. NGODING TIPIS-TIPIS DI 📋
/lib/view/mahasiswa_list.dart
import 'package:flutter/material.dart';
import '../models/mahasiswa_model.dart';
import '../services/mahasiswa_service.dart';
import 'mahasiswa_add.dart';
import 'mahasiswa_detail.dart';
class MahasiswaListView extends StatefulWidget {
const MahasiswaListView({super.key});
@override
State<MahasiswaListView> createState() => _MahasiswaListViewState();
}
class _MahasiswaListViewState extends State<MahasiswaListView> {
final MahasiswaService _service = MahasiswaService();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Data Mahasiswa'),
actions: [
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const MahasiswaAddView()),
).then((_) => setState(() {})); // Refresh after adding
},
),
],
),
body: FutureBuilder<List<Mahasiswa>>(
future: _service.fetchMahasiswa(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
}
final mahasiswaList = snapshot.data ?? [];
if (mahasiswaList.isEmpty) {
return const Center(child: Text('Belum ada data mahasiswa.'));
}
return ListView.builder(
itemCount: mahasiswaList.length,
itemBuilder: (context, index) {
final mahasiswa = mahasiswaList[index];
return ListTile(
title: Text(mahasiswa.nama),
subtitle: Text('NIM: ${mahasiswa.nim}'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
MahasiswaDetailView(mahasiswa: mahasiswa),
),
);
},
);
},
);
},
),
);
}
}
05.3. NGODING TIPIS-TIPIS DI ➕
/lib/view/mahasiswa_add.dart
import 'package:flutter/material.dart';
import '../models/mahasiswa_model.dart';
import '../services/mahasiswa_service.dart';
class MahasiswaAddView extends StatefulWidget {
const MahasiswaAddView({super.key});
@override
State<MahasiswaAddView> createState() => _MahasiswaAddViewState();
}
class _MahasiswaAddViewState extends State<MahasiswaAddView> {
final _formKey = GlobalKey<FormState>();
final MahasiswaService _service = MahasiswaService();
final _namaController = TextEditingController();
final _nimController = TextEditingController();
final _prodiController = TextEditingController();
final _jurusanController = TextEditingController();
void _saveMahasiswa() {
if (_formKey.currentState!.validate()) {
final newMahasiswa = Mahasiswa(
id: '',
nama: _namaController.text,
nim: _nimController.text,
prodi: _prodiController.text,
jurusan: _jurusanController.text,
);
_service.addMahasiswa(newMahasiswa).then((_) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Data berhasil ditambahkan.')));
Navigator.pop(context);
}).catchError((error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Gagal menyimpan data: $error')),
);
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Tambah Mahasiswa')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _namaController,
decoration: const InputDecoration(labelText: 'Nama'),
validator: (value) =>
value!.isEmpty ? 'Nama tidak boleh kosong' : null,
),
TextFormField(
controller: _nimController,
decoration: const InputDecoration(labelText: 'NIM'),
validator: (value) =>
value!.isEmpty ? 'NIM tidak boleh kosong' : null,
),
TextFormField(
controller: _prodiController,
decoration: const InputDecoration(labelText: 'Prodi'),
),
TextFormField(
controller: _jurusanController,
decoration: const InputDecoration(labelText: 'Jurusan'),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _saveMahasiswa,
child: const Text('Simpan'),
),
],
),
),
),
);
}
}
05.4. NGODING TIPIS-TIPIS DI 👀
/lib/view/mahasiswa_detail.dart
import 'package:flutter/material.dart';
import '../models/mahasiswa_model.dart';
import 'mahasiswa_edit.dart';
import '../services/mahasiswa_service.dart';
class MahasiswaDetailView extends StatelessWidget {
final Mahasiswa mahasiswa;
const MahasiswaDetailView({super.key, required this.mahasiswa});
@override
Widget build(BuildContext context) {
final MahasiswaService _service = MahasiswaService();
return Scaffold(
appBar: AppBar(
title: Text(mahasiswa.nama),
actions: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MahasiswaEditView(mahasiswa: mahasiswa),
),
).then((_) => Navigator.pop(context)); // Refresh on back
},
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
_service.deleteMahasiswa(mahasiswa.id).then((_) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Data berhasil dihapus.')),
);
Navigator.pop(context);
}).catchError((error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Gagal menghapus data: $error')),
);
});
},
),
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Nama: ${mahasiswa.nama}',
style: const TextStyle(fontSize: 18)),
const SizedBox(height: 10),
Text('NIM: ${mahasiswa.nim}', style: const TextStyle(fontSize: 18)),
const SizedBox(height: 10),
Text('Prodi: ${mahasiswa.prodi}',
style: const TextStyle(fontSize: 18)),
const SizedBox(height: 10),
Text('Jurusan: ${mahasiswa.jurusan}',
style: const TextStyle(fontSize: 18)),
],
),
),
);
}
}
05.5. NGODING TIPIS-TIPIS DI ✏️
/lib/view/mahasiswa_edit.dart
import 'package:flutter/material.dart';
import '../models/mahasiswa_model.dart';
import '../services/mahasiswa_service.dart';
class MahasiswaEditView extends StatefulWidget {
final Mahasiswa mahasiswa;
const MahasiswaEditView({super.key, required this.mahasiswa});
@override
State<MahasiswaEditView> createState() => _MahasiswaEditViewState();
}
class _MahasiswaEditViewState extends State<MahasiswaEditView> {
final _formKey = GlobalKey<FormState>();
final _namaController = TextEditingController();
final _nimController = TextEditingController();
final _prodiController = TextEditingController();
final _jurusanController = TextEditingController();
final MahasiswaService _service = MahasiswaService();
@override
void initState() {
super.initState();
_namaController.text = widget.mahasiswa.nama;
_nimController.text = widget.mahasiswa.nim;
_prodiController.text = widget.mahasiswa.prodi;
_jurusanController.text = widget.mahasiswa.jurusan;
}
void _updateMahasiswa() {
if (_formKey.currentState!.validate()) {
final updatedMahasiswa = Mahasiswa(
id: widget.mahasiswa.id,
nama: _namaController.text,
nim: _nimController.text,
prodi: _prodiController.text,
jurusan: _jurusanController.text,
);
_service.updateMahasiswa(widget.mahasiswa.id, updatedMahasiswa).then((_) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Data berhasil diperbarui.')),
);
Navigator.pop(context);
}).catchError((error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Gagal memperbarui data: $error')),
);
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Edit Mahasiswa')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _namaController,
decoration: const InputDecoration(labelText: 'Nama'),
validator: (value) =>
value!.isEmpty ? 'Nama tidak boleh kosong' : null,
),
TextFormField(
controller: _nimController,
decoration: const InputDecoration(labelText: 'NIM'),
validator: (value) =>
value!.isEmpty ? 'NIM tidak boleh kosong' : null,
),
TextFormField(
controller: _prodiController,
decoration: const InputDecoration(labelText: 'Prodi'),
),
TextFormField(
controller: _jurusanController,
decoration: const InputDecoration(labelText: 'Jurusan'),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _updateMahasiswa,
child: const Text('Perbarui'),
),
],
),
),
),
);
}
}