Aplikasi Jadwal Pelajaran Sederhana v 0.2

Thumbnail Praktikum Flutter

📱 Praktikum Flutter : Aplikasi Jadwal Pelajaran Sederhana

🎯 CRUD + setState + Local Storage (SharedPreferences) + Konfirmasi Hapus

📅 14 April 2026 | ✏️ Wahana Belajar dan Berbagi

🧑‍💻 TUGAS PRAKTIKUM FLUTTER HARI INI

Topik: Aplikasi Jadwal Pelajaran (CRUD)

  1. Jalankan aplikasi dari kode yang diberikan
  2. Pastikan fitur berjalan: ✅ Tambah ✅ Edit ✅ Hapus
  3. Modifikasi (minimal 2):
    • ➕ Tambah hari & jam
    • 🎨 Percantik tampilan (warna / font)
    • ⚠️ Konfirmasi sebelum hapus
  4. Dokumentasikan SEMUA proses di blog masing-masing:
    • 📸 Screenshot tampilan utama
    • 📸 Screenshot tambah data
    • 📸 Screenshot edit data
  5. Kumpulkan: Kirim link postingan blog kalian di kolom komentar postingan guru

🔥 Minimal aplikasi jalan + CRUD berhasil
🚀 Nilai lebih kalau tampilan dimodifikasi & rapi

Selamat mencoba! 💪

✅ Berikut contoh kode praktikum Flutter yang sudah sesuai dengan ketentuan tugas (Tambah hari & jam + Konfirmasi hapus + Tampilan cantik). Boleh menggunakan code program ini, boleh juga cari referensi lain asalkan memenuhi kriteria tugas.

👉 Cocok untuk pembelajaran mandiri hari ini 💻📱

💡 Kenapa Kode Ini Disarankan?

  • Memenuhi SEMUA kriteria tugas – Hari & jam ✅, Konfirmasi hapus ✅, Tampilan cantik ✅
  • CRUD Lengkap – Tambah, Edit, Hapus semua pakai setState
  • Penyimpanan Permanen – Pakai SharedPreferences, data tidak hilang saat aplikasi ditutup
  • UI Fresh & Modern – Gradient appbar, warna card dinamis, shadow, animasi
  • Search & Filter Hari – Fitur pencarian dan filter jadwal berdasarkan hari
  • Konfirmasi Sebelum Hapus – AlertDialog agar tidak salah hapus
  • SnackBar Notifikasi – Ada notifikasi saat tambah/edit/hapus berhasil
  • Siap Running di Zapp.run atau VS Code – Tinggal tambah dependency shared_preferences
lib/main.dart

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Jadwal Pelajaran',
      theme: ThemeData(primarySwatch: Colors.indigo, useMaterial3: true, fontFamily: 'Poppins'),
      darkTheme: ThemeData(brightness: Brightness.dark, useMaterial3: true),
      themeMode: ThemeMode.system,
      home: JadwalPage(),
    );
  }
}

// ==================== MODEL DATA ====================
class JadwalModel {
  String id, mataPelajaran, hari, jam, ruang, guru;
  int warnaIndeks;
  JadwalModel({required this.id, required this.mataPelajaran, required this.hari, required this.jam, required this.ruang, this.guru = '', this.warnaIndeks = 0});
  Map<String,dynamic> toJson() => {'id':id,'mataPelajaran':mataPelajaran,'hari':hari,'jam':jam,'ruang':ruang,'guru':guru,'warnaIndeks':warnaIndeks};
  factory JadwalModel.fromJson(Map<String,dynamic> json) => JadwalModel(id:json['id'],mataPelajaran:json['mataPelajaran'],hari:json['hari'],jam:json['jam'],ruang:json['ruang'],guru:json['guru']??'',warnaIndeks:json['warnaIndeks']??0);
}

// ==================== HALAMAN UTAMA ====================
class JadwalPage extends StatefulWidget {
  @override
  _JadwalPageState createState() => _JadwalPageState();
}

class _JadwalPageState extends State<JadwalPage> {
  List<JadwalModel> jadwalList = [];
  List<JadwalModel> filteredList = [];
  String searchQuery = '';
  String filterHari = 'Semua';
  final List<String> daftarHari = ['Semua','Senin','Selasa','Rabu','Kamis','Jumat','Sabtu'];

  final List<Map<String,dynamic>> warnaCard = [
    {'warna': Colors.blue.shade50, 'aksen': Colors.blue.shade700, 'icon': Icons.school},
    {'warna': Colors.green.shade50, 'aksen': Colors.green.shade700, 'icon': Icons.calculate},
    {'warna': Colors.orange.shade50, 'aksen': Colors.orange.shade700, 'icon': Icons.language},
    {'warna': Colors.purple.shade50, 'aksen': Colors.purple.shade700, 'icon': Icons.code},
    {'warna': Colors.red.shade50, 'aksen': Colors.red.shade700, 'icon': Icons.art_track},
    {'warna': Colors.teal.shade50, 'aksen': Colors.teal.shade700, 'icon': Icons.fitness_center},
  ];

  @override
  void initState() { super.initState(); _loadData(); }

  // ==================== LOCAL STORAGE ====================
  Future<void> _saveData() async {
    final prefs = await SharedPreferences.getInstance();
    List<String> jsonList = jadwalList.map((j) => jsonEncode(j.toJson())).toList();
    await prefs.setStringList('jadwal_list', jsonList);
  }

  Future<void> _loadData() async {
    final prefs = await SharedPreferences.getInstance();
    List<String>? jsonList = prefs.getStringList('jadwal_list');
    if (jsonList != null && jsonList.isNotEmpty) {
      setState(() { jadwalList = jsonList.map((j) => JadwalModel.fromJson(jsonDecode(j))).toList(); _filterData(); });
    } else {
      setState(() {
        jadwalList = [JadwalModel(id: DateTime.now().millisecondsSinceEpoch.toString(), mataPelajaran: 'Pemrograman Flutter', hari: 'Senin', jam: '07:30 - 09:30', ruang: 'Lab 1', guru: 'Bpk. Darsu', warnaIndeks: 0)];
        _saveData(); _filterData();
      });
    }
  }

  void _filterData() => setState(() {
    filteredList = jadwalList.where((j) =>
        (filterHari == 'Semua' || j.hari == filterHari) &&
        (j.mataPelajaran.toLowerCase().contains(searchQuery.toLowerCase()) ||
         j.guru.toLowerCase().contains(searchQuery.toLowerCase()))
    ).toList();
  });

  // ==================== CRUD OPERATIONS ====================
  void tambahData(JadwalModel j) { setState(() { jadwalList.add(j); _saveData(); _filterData(); }); }
  void editData(int idx, JadwalModel j) { setState(() { jadwalList[idx] = j; _saveData(); _filterData(); }); }

  // 🔥 KONFIRMASI SEBELUM HAPUS (Sesuai Tugas!)
  void konfirmasiHapus(int index) {
    showDialog(context: context, builder: (_) => AlertDialog(
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
      title: Row(children: [Icon(Icons.warning_amber_rounded, color: Colors.red), SizedBox(width: 10), Text('Hapus Jadwal?')]),
      content: Text('Yakin ingin menghapus "${jadwalList[index].mataPelajaran}"?'),
      actions: [
        TextButton(onPressed: () => Navigator.pop(_), child: Text('Batal')),
        ElevatedButton(onPressed: () {
          setState(() { jadwalList.removeAt(index); _saveData(); _filterData(); });
          Navigator.pop(_);
          ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Jadwal dihapus'), backgroundColor: Colors.red));
        }, style: ElevatedButton.styleFrom(backgroundColor: Colors.red), child: Text('Hapus')),
      ],
    ));
  }

  // ==================== DIALOG TAMBAH/EDIT ====================
  void tampilDialog({JadwalModel? jadwalLama, int? index}) {
    bool isEdit = jadwalLama != null;
    TextEditingController mapelC = TextEditingController(text: jadwalLama?.mataPelajaran ?? '');
    TextEditingController jamC = TextEditingController(text: jadwalLama?.jam ?? '');
    TextEditingController ruangC = TextEditingController(text: jadwalLama?.ruang ?? '');
    TextEditingController guruC = TextEditingController(text: jadwalLama?.guru ?? '');
    String selectedHari = jadwalLama?.hari ?? daftarHari[1];
    int selectedWarna = jadwalLama?.warnaIndeks ?? 0;

    showModalBottomSheet(context: context, isScrollControlled: true, shape: RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(25))), builder: (_) {
      return StatefulBuilder(builder: (ctx, setStateBottom) {
        return Container(padding: EdgeInsets.only(bottom: MediaQuery.of(ctx).viewInsets.bottom, left: 20, right: 20, top: 20),
          child: SingleChildScrollView(child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
            Center(child: Container(width: 50, height: 5, decoration: BoxDecoration(color: Colors.grey.shade300, borderRadius: BorderRadius.circular(10)))),
            SizedBox(height: 20),
            Text(isEdit ? '✏️ Edit Jadwal' : '➕ Tambah Jadwal', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
            SizedBox(height: 20),
            TextField(controller: mapelC, decoration: InputDecoration(labelText: 'Mata Pelajaran', prefixIcon: Icon(Icons.book), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)))),
            SizedBox(height: 15),
            DropdownButtonFormField<String>(value: selectedHari, decoration: InputDecoration(labelText: 'Hari', prefixIcon: Icon(Icons.calendar_today), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12))),
              items: daftarHari.skip(1).map((h) => DropdownMenuItem(value: h, child: Text(h))).toList(),
              onChanged: (v) => setStateBottom(() => selectedHari = v!)),
            SizedBox(height: 15),
            TextField(controller: jamC, decoration: InputDecoration(labelText: 'Jam', hintText: '07:30 - 09:00', prefixIcon: Icon(Icons.access_time), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)))),
            SizedBox(height: 15),
            TextField(controller: ruangC, decoration: InputDecoration(labelText: 'Ruang', prefixIcon: Icon(Icons.location_on), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)))),
            SizedBox(height: 15),
            TextField(controller: guruC, decoration: InputDecoration(labelText: 'Guru Pengajar', prefixIcon: Icon(Icons.person), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)))),
            SizedBox(height: 15),
            Text('🎨 Warna Card', style: TextStyle(fontWeight: FontWeight.bold)),
            SizedBox(height: 10),
            Container(height: 50, child: ListView.builder(scrollDirection: Axis.horizontal, itemCount: warnaCard.length, itemBuilder: (_, i) => GestureDetector(onTap: () => setStateBottom(() => selectedWarna = i),
              child: Container(margin: EdgeInsets.only(right: 10), width: 50, decoration: BoxDecoration(color: warnaCard[i]['warna'], border: Border.all(color: selectedWarna == i ? warnaCard[i]['aksen'] : Colors.transparent, width: 3), borderRadius: BorderRadius.circular(25)),
              child: Icon(warnaCard[i]['icon'], color: warnaCard[i]['aksen'])))),
            SizedBox(height: 20),
            Row(children: [
              Expanded(child: OutlinedButton(onPressed: () => Navigator.pop(ctx), child: Text('Batal'), style: OutlinedButton.styleFrom(padding: EdgeInsets.symmetric(vertical: 15), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))))),
              SizedBox(width: 10),
              Expanded(child: ElevatedButton(onPressed: () {
                if (mapelC.text.isNotEmpty) {
                  if (isEdit) editData(index!, JadwalModel(id: jadwalLama!.id, mataPelajaran: mapelC.text, hari: selectedHari, jam: jamC.text, ruang: ruangC.text, guru: guruC.text, warnaIndeks: selectedWarna));
                  else tambahData(JadwalModel(id: DateTime.now().millisecondsSinceEpoch.toString(), mataPelajaran: mapelC.text, hari: selectedHari, jam: jamC.text, ruang: ruangC.text, guru: guruC.text, warnaIndeks: selectedWarna));
                  Navigator.pop(ctx);
                  ScaffoldMessenger.of(ctx).showSnackBar(SnackBar(content: Text(isEdit ? 'Jadwal diupdate' : 'Jadwal ditambahkan'), backgroundColor: Colors.green));
                }
              }, style: ElevatedButton.styleFrom(padding: EdgeInsets.symmetric(vertical: 15), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))), child: Text(isEdit ? 'Update' : 'Simpan'))),
            ]),
            SizedBox(height: 20),
          ])),
        );
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(slivers: [
        SliverAppBar(expandedHeight: 120, floating: true, pinned: true, title: Text('📅 Jadwal Pelajaran', style: TextStyle(fontWeight: FontWeight.bold)),
          flexibleSpace: FlexibleSpaceBar(background: Container(decoration: BoxDecoration(gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Colors.indigo.shade700, Colors.blue.shade600, Colors.cyan.shade500])))),
          actions: [IconButton(icon: Icon(Icons.info_outline), onPressed: () => showAboutDialog(context: context, applicationName: 'Jadwal Pelajaran', children: [Text('CRUD + Local Storage + Konfirmasi Hapus')]))]),
        SliverToBoxAdapter(child: Padding(padding: EdgeInsets.all(16), child: Column(children: [
          Container(decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(30), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10)]),
            child: TextField(onChanged: (v) { searchQuery = v; _filterData(); }, decoration: InputDecoration(hintText: '🔍 Cari jadwal...', prefixIcon: Icon(Icons.search), border: OutlineInputBorder(borderRadius: BorderRadius.circular(30), borderSide: BorderSide.none), contentPadding: EdgeInsets.symmetric(vertical: 15)))),
          SizedBox(height: 15),
          Container(height: 45, child: ListView.builder(scrollDirection: Axis.horizontal, itemCount: daftarHari.length, itemBuilder: (_, i) => Padding(padding: EdgeInsets.only(right: 8),
            child: FilterChip(label: Text(daftarHari[i]), selected: filterHari == daftarHari[i], onSelected: (_) => setState(() { filterHari = daftarHari[i]; _filterData(); }), selectedColor: Colors.indigo.shade600, labelStyle: TextStyle(color: filterHari == daftarHari[i] ? Colors.white : null))))),
        ]))),
        filteredList.isEmpty ? SliverToBoxAdapter(child: Container(height: 300, child: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [Icon(Icons.schedule, size: 80, color: Colors.grey), Text('Tidak ada jadwal', style: TextStyle(fontSize: 18)), Text('Tekan tombol + untuk menambah jadwal')]))))
        : SliverPadding(padding: EdgeInsets.symmetric(horizontal: 16), sliver: SliverList(delegate: SliverChildBuilderDelegate((_, i) {
          var j = filteredList[i];
          var warna = warnaCard[j.warnaIndeks % warnaCard.length];
          int originalIdx = jadwalList.indexOf(j);
          return Card(elevation: 3, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), margin: EdgeInsets.only(bottom: 12),
            child: Container(decoration: BoxDecoration(color: warna['warna'], borderRadius: BorderRadius.circular(16)),
              child: ListTile(contentPadding: EdgeInsets.all(16), leading: CircleAvatar(radius: 28, backgroundColor: warna['aksen'], child: Icon(warna['icon'], color: Colors.white)),
                title: Text(j.mataPelajaran, style: TextStyle(fontWeight: FontWeight.bold, color: warna['aksen'])),
                subtitle: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
                  Row(children: [Icon(Icons.calendar_today, size: 14, color: warna['aksen']), SizedBox(width: 5), Text(j.hari), SizedBox(width: 15), Icon(Icons.access_time, size: 14, color: warna['aksen']), SizedBox(width: 5), Text(j.jam)]),
                  if (j.ruang.isNotEmpty) Row(children: [Icon(Icons.location_on, size: 14, color: warna['aksen']), SizedBox(width: 5), Text(j.ruang)]),
                  if (j.guru.isNotEmpty) Row(children: [Icon(Icons.person, size: 14, color: warna['aksen']), SizedBox(width: 5), Text(j.guru)]),
                ]),
                trailing: Row(mainAxisSize: MainAxisSize.min, children: [
                  IconButton(icon: Icon(Icons.edit, color: Colors.blue), onPressed: () => tampilDialog(jadwalLama: j, index: originalIdx)),
                  IconButton(icon: Icon(Icons.delete, color: Colors.red), onPressed: () => konfirmasiHapus(originalIdx))
                ])
              )
            )
          );
        }, childCount: filteredList.length))),
        SliverPadding(padding: EdgeInsets.only(bottom: 80)),
      ]),
      floatingActionButton: FloatingActionButton.extended(onPressed: () => tampilDialog(), icon: Icon(Icons.add), label: Text('Tambah Jadwal'), backgroundColor: Colors.indigo.shade700),
    );
  }
}
      

✅ Checklist Pemenuhan Tugas

🎯 Wajib:

  • ✅ Tambah data
  • ✅ Edit data
  • ✅ Hapus data

🎨 Modifikasi (Minimal 2):

  • Tambah Hari & Jam (Dropdown Hari + TextField Jam)
  • Percantik Tampilan (Gradient, warna card, shadow, animasi)
  • Konfirmasi Sebelum Hapus (AlertDialog dengan opsi Batal/Hapus)

🔥 Kode ini sudah memenuhi SEMUA kriteria tugas! 🔥

🚀 Cara Menjalankan di Zapp.run

  1. Buka zapp.run
  2. Pilih New Flutter Project
  3. Di file pubspec.yaml, tambahkan: shared_preferences: ^2.2.2
  4. Copy paste kode di atas ke lib/main.dart
  5. Klik Run dan aplikasi akan berjalan!

⚠️ Pastikan dependency shared_preferences sudah ditambahkan, jika tidak akan error!

📸 Tugas Yang Harus Dikerjakan Siswa

  1. Jalankan aplikasi dari kode yang diberikan
  2. Pastikan fitur berjalan: Tambah ✅ Edit ✅ Hapus ✅
  3. Dokumentasikan SEMUA proses di blog masing-masing:
    • 📸 Screenshot tampilan utama
    • 📸 Screenshot saat tambah data
    • 📸 Screenshot saat edit data
    • (boleh tambah penjelasan singkat)
  4. Kumpulkan:
    • 👉 Kirim link postingan blog kalian
    • 👉 Tulis di kolom komentar postingan guru

💡 Tips: Modifikasi tampilan semaksimal mungkin untuk nilai lebih! Ganti warna, font, atau tambah fitur keren lainnya.

📌 Catatan Penting

Kode ini sengaja dibuat agar:

  • Memenuhi semua kriteria tugas (Hari & jam + Konfirmasi hapus + Tampilan cantik)
  • Tidak terlalu kompleks – mudah dipahami pemula
  • Mudah dimodifikasi – struktur jelas, pisah model dan widget
  • Minim error – menggunakan state management setState standar Flutter
  • Penyimpanan permanen dengan SharedPreferences – tanpa database rumit
📘 Facebook 🐦 Twitter 📧 Email 📱 WhatsApp

Copyright © 2026 WAHANA BELAJAR DAN BERBAGI | Powered by Blogger

Design by FlexiThemes | Blogger Theme by NewBloggerThemes.com

Komentar

Postingan populer dari blog ini

Kunci Jawaban Soal Pilihan Ganda Flutter UI (State Management)

🎮 Review Aplikasi Flutter: Pixel Quest To-Do