Membuat Aplikasi jadwal pelajaran sederhana dengan flutter

 

📝 Aplikasi Jadwal Pelajaran Sederhana dengan Flutter

Pada praktikum ini dibuat aplikasi Jadwal Pelajaran Sederhana menggunakan Flutter dengan fitur:

  • Tambah data

  • Edit data

  • Hapus data

  • Validasi input (huruf & angka sesuai kolom)

  • Tampilan modern tema kuning-hitam

Aplikasi menggunakan Stateful Widget untuk mengelola data dan ListView untuk menampilkan daftar jadwal.


⚙️ Konsep Utama

Beberapa konsep yang digunakan:

  • State Management (setState) → untuk update tampilan saat data berubah

  • Form & Validasi → memastikan input sesuai format (huruf / angka)

  • ListView → menampilkan data jadwal

  • Dialog → untuk tambah, edit, dan konfirmasi hapus

  • Custom UI → menggunakan tema gelap + aksen kuning


💡 Validasi Input

Setiap field memiliki aturan:

  • Mata pelajaran & hari → hanya huruf

  • Jam → hanya angka

  • Ruang → huruf + angka

Hal ini dilakukan menggunakan TextInputFormatter dan validator.


🧩 Kode Lengkap

Berikut adalah implementasi lengkap dari aplikasi:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(const JadwalApp());
}

class JadwalApp extends StatelessWidget {
  const JadwalApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Jadwal Pelajaran',
      theme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFFFFC107),
          brightness: Brightness.dark,
        ),
        scaffoldBackgroundColor: const Color(0xFF0E0E0E),
        appBarTheme: const AppBarTheme(
          backgroundColor: Color(0xFF111111),
          foregroundColor: Colors.amber,
          centerTitle: true,
          elevation: 0,
        ),
      ),
      home: const JadwalPage(),
    );
  }
}

class JadwalItem {
  final String mapel;
  final String hari;
  final String jam;
  final String ruang;

  JadwalItem({
    required this.mapel,
    required this.hari,
    required this.jam,
    required this.ruang,
  });
}

class JadwalPage extends StatefulWidget {
  const JadwalPage({super.key});

  @override
  State<JadwalPage> createState() => _JadwalPageState();
}

class _JadwalPageState extends State<JadwalPage> {
  final List<JadwalItem> _data = [
    JadwalItem(
      mapel: 'Matematika',
      hari: 'Senin',
      jam: '1',
      ruang: 'X RPL 1',
    ),
    JadwalItem(
      mapel: 'Pemrograman',
      hari: 'Selasa',
      jam: '2',
      ruang: 'Lab Komputer',
    ),
    JadwalItem(
      mapel: 'Bahasa Indonesia',
      hari: 'Rabu',
      jam: '3',
      ruang: 'XI RPL 1',
    ),
  ];

  int get _totalMapelUnik => _data.map((e) => e.mapel.toLowerCase()).toSet().length;

  void _tampilForm({JadwalItem? item, int? index}) {
    final formKey = GlobalKey<FormState>();
    final mapelController = TextEditingController(text: item?.mapel ?? '');
    final hariController = TextEditingController(text: item?.hari ?? '');
    final jamController = TextEditingController(text: item?.jam ?? '');
    final ruangController = TextEditingController(text: item?.ruang ?? '');

    showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          backgroundColor: const Color(0xFF171717),
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
          titlePadding: const EdgeInsets.fromLTRB(20, 18, 20, 10),
          contentPadding: const EdgeInsets.fromLTRB(20, 8, 20, 0),
          actionsPadding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
          title: Row(
            children: [
              Container(
                width: 42,
                height: 42,
                decoration: BoxDecoration(
                  color: Colors.amber,
                  borderRadius: BorderRadius.circular(12),
                ),
                child: const Icon(Icons.edit_note, color: Colors.black),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: Text(
                  item == null ? 'Tambah Jadwal' : 'Edit Jadwal',
                  style: const TextStyle(fontWeight: FontWeight.bold),
                ),
              ),
            ],
          ),
          content: SingleChildScrollView(
            child: Form(
              key: formKey,
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  _buildInput(
                    controller: mapelController,
                    label: 'Mata Pelajaran',
                    icon: Icons.menu_book,
                    hint: 'Huruf saja, contoh: Matematika',
                    keyboardType: TextInputType.name,
                    inputFormatters: [
                      FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z\s]')),
                    ],
                    validator: (value) {
                      final text = value?.trim() ?? '';
                      if (text.isEmpty) return 'Mata pelajaran wajib diisi';
                      if (!RegExp(r'^[a-zA-Z\s]+$').hasMatch(text)) {
                        return 'Gunakan huruf saja';
                      }
                      return null;
                    },
                  ),
                  const SizedBox(height: 12),
                  _buildInput(
                    controller: hariController,
                    label: 'Hari',
                    icon: Icons.calendar_month,
                    hint: 'Huruf saja, contoh: Senin',
                    keyboardType: TextInputType.name,
                    inputFormatters: [
                      FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z\s]')),
                    ],
                    validator: (value) {
                      final text = value?.trim() ?? '';
                      if (text.isEmpty) return 'Hari wajib diisi';
                      if (!RegExp(r'^[a-zA-Z\s]+$').hasMatch(text)) {
                        return 'Gunakan huruf saja';
                      }
                      return null;
                    },
                  ),
                  const SizedBox(height: 12),
                  _buildInput(
                    controller: jamController,
                    label: 'Jam Pelajaran',
                    icon: Icons.looks_one,
                    hint: 'Angka saja, contoh: 1',
                    keyboardType: TextInputType.number,
                    inputFormatters: [
                      FilteringTextInputFormatter.digitsOnly,
                    ],
                    validator: (value) {
                      final text = value?.trim() ?? '';
                      if (text.isEmpty) return 'Jam pelajaran wajib diisi';
                      if (!RegExp(r'^[0-9]+$').hasMatch(text)) {
                        return 'Gunakan angka saja';
                      }
                      return null;
                    },
                  ),
                  const SizedBox(height: 12),
                  _buildInput(
                    controller: ruangController,
                    label: 'Kelas / Ruang',
                    icon: Icons.location_on,
                    hint: 'Contoh: X RPL 1',
                    keyboardType: TextInputType.text,
                    inputFormatters: [
                      FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9\s]')),
                    ],
                    validator: (value) {
                      final text = value?.trim() ?? '';
                      if (text.isEmpty) return 'Ruang wajib diisi';
                      if (!RegExp(r'^[a-zA-Z0-9\s]+$').hasMatch(text)) {
                        return 'Gunakan huruf atau angka';
                      }
                      return null;
                    },
                  ),
                  const SizedBox(height: 4),
                ],
              ),
            ),
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('Batal'),
            ),
            ElevatedButton.icon(
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.amber,
                foregroundColor: Colors.black,
                padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
              ),
              onPressed: () {
                if (!(formKey.currentState?.validate() ?? false)) return;

                setState(() {
                  final dataBaru = JadwalItem(
                    mapel: mapelController.text.trim(),
                    hari: hariController.text.trim(),
                    jam: jamController.text.trim(),
                    ruang: ruangController.text.trim(),
                  );

                  if (item == null) {
                    _data.add(dataBaru);
                  } else {
                    _data[index!] = dataBaru;
                  }
                });

                Navigator.pop(context);
              },
              icon: const Icon(Icons.save),
              label: const Text('Simpan'),
            ),
          ],
        );
      },
    );
  }

  void _hapusData(int index) {
    showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          backgroundColor: const Color(0xFF171717),
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
          title: Row(
            children: const [
              Icon(Icons.warning_amber_rounded, color: Colors.amber),
              SizedBox(width: 10),
              Text('Hapus Jadwal'),
            ],
          ),
          content: const Text('Yakin ingin menghapus data ini?'),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('Batal'),
            ),
            ElevatedButton.icon(
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.redAccent,
                foregroundColor: Colors.white,
              ),
              onPressed: () {
                setState(() {
                  _data.removeAt(index);
                });
                Navigator.pop(context);
              },
              icon: const Icon(Icons.delete),
              label: const Text('Hapus'),
            ),
          ],
        );
      },
    );
  }

  Widget _buildInput({
    required TextEditingController controller,
    required String label,
    required IconData icon,
    required String hint,
    required TextInputType keyboardType,
    required List<TextInputFormatter> inputFormatters,
    required String? Function(String?) validator,
  }) {
    return TextFormField(
      controller: controller,
      keyboardType: keyboardType,
      inputFormatters: inputFormatters,
      style: const TextStyle(color: Colors.white),
      validator: validator,
      decoration: InputDecoration(
        labelText: label,
        hintText: hint,
        hintStyle: TextStyle(color: Colors.grey.shade600),
        labelStyle: const TextStyle(color: Colors.amber),
        prefixIcon: Icon(icon, color: Colors.amber),
        filled: true,
        fillColor: const Color(0xFF111111),
        enabledBorder: OutlineInputBorder(
          borderRadius: BorderRadius.circular(14),
          borderSide: BorderSide(color: Colors.amber.withOpacity(0.55)),
        ),
        focusedBorder: OutlineInputBorder(
          borderRadius: BorderRadius.circular(14),
          borderSide: const BorderSide(color: Colors.amber, width: 1.5),
        ),
        errorBorder: OutlineInputBorder(
          borderRadius: BorderRadius.circular(14),
          borderSide: const BorderSide(color: Colors.redAccent),
        ),
        focusedErrorBorder: OutlineInputBorder(
          borderRadius: BorderRadius.circular(14),
          borderSide: const BorderSide(color: Colors.redAccent, width: 1.5),
        ),
      ),
    );
  }

  Color _warnaCard(int index) {
    final warna = [
      const Color(0xFF181818),
      const Color(0xFF1D1D1D),
      const Color(0xFF222222),
    ];
    return warna[index % warna.length];
  }

  Widget _buildStat(String label, String value, IconData icon) {
    return Expanded(
      child: Container(
        padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 14),
        decoration: BoxDecoration(
          color: const Color(0xFF1A1A1A),
          borderRadius: BorderRadius.circular(18),
          border: Border.all(color: Colors.amber.withOpacity(0.35)),
        ),
        child: Column(
          children: [
            Container(
              width: 40,
              height: 40,
              decoration: BoxDecoration(
                color: Colors.amber.withOpacity(0.14),
                borderRadius: BorderRadius.circular(12),
              ),
              child: Icon(icon, color: Colors.amber),
            ),
            const SizedBox(height: 8),
            Text(
              value,
              style: const TextStyle(
                fontSize: 22,
                fontWeight: FontWeight.bold,
                color: Colors.white,
              ),
            ),
            const SizedBox(height: 4),
            Text(
              label,
              textAlign: TextAlign.center,
              style: TextStyle(
                color: Colors.grey.shade400,
                fontSize: 12,
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildHeader() {
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.all(18),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(22),
        gradient: const LinearGradient(
          colors: [Color(0xFFFFC107), Color(0xFFFFE082)],
        ),
        boxShadow: [
          BoxShadow(
            color: Colors.amber.withOpacity(0.25),
            blurRadius: 18,
            offset: const Offset(0, 8),
          ),
        ],
      ),
      child: const Row(
        children: [
          CircleAvatar(
            radius: 28,
            backgroundColor: Colors.black,
            child: Icon(Icons.school, color: Colors.amber, size: 30),
          ),
          SizedBox(width: 14),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'Jadwal Pelajaran Sederhana',
                  style: TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                    color: Colors.black,
                  ),
                ),
                SizedBox(height: 4),
                Text(
                  'Aplikasi praktikum Flutter kelas sekolah',
                  style: TextStyle(
                    fontSize: 13,
                    color: Colors.black87,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildEmptyState() {
    return Center(
      child: Container(
        padding: const EdgeInsets.all(24),
        decoration: BoxDecoration(
          color: const Color(0xFF1A1A1A),
          borderRadius: BorderRadius.circular(20),
          border: Border.all(color: Colors.amber.withOpacity(0.25)),
        ),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(
              Icons.event_busy,
              size: 72,
              color: Colors.amber.withOpacity(0.9),
            ),
            const SizedBox(height: 12),
            const Text(
              'Belum ada jadwal',
              style: TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
                color: Colors.white,
              ),
            ),
            const SizedBox(height: 6),
            Text(
              'Tekan tombol tambah untuk membuat jadwal baru',
              textAlign: TextAlign.center,
              style: TextStyle(color: Colors.grey.shade400),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildListItem(JadwalItem item, int index) {
    return Container(
      decoration: BoxDecoration(
        color: _warnaCard(index),
        borderRadius: BorderRadius.circular(18),
        border: Border.all(color: Colors.amber.withOpacity(0.24)),
      ),
      child: Padding(
        padding: const EdgeInsets.all(14),
        child: Row(
          children: [
            Container(
              width: 54,
              height: 54,
              decoration: BoxDecoration(
                color: Colors.amber,
                borderRadius: BorderRadius.circular(16),
              ),
              child: const Icon(
                Icons.menu_book,
                color: Colors.black,
                size: 30,
              ),
            ),
            const SizedBox(width: 12),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    item.mapel,
                    style: const TextStyle(
                      fontSize: 17,
                      fontWeight: FontWeight.bold,
                      color: Colors.white,
                    ),
                  ),
                  const SizedBox(height: 8),
                  Row(
                    children: [
                      const Icon(Icons.calendar_month, size: 16, color: Colors.amber),
                      const SizedBox(width: 6),
                      Text(
                        item.hari,
                        style: TextStyle(color: Colors.grey.shade300),
                      ),
                      const SizedBox(width: 12),
                      const Icon(Icons.looks_one, size: 16, color: Colors.amber),
                      const SizedBox(width: 6),
                      Text(
                        'Jam ${item.jam}',
                        style: TextStyle(color: Colors.grey.shade300),
                      ),
                    ],
                  ),
                  const SizedBox(height: 6),
                  Row(
                    children: [
                      const Icon(Icons.location_on, size: 16, color: Colors.amber),
                      const SizedBox(width: 6),
                      Text(
                        item.ruang,
                        style: TextStyle(color: Colors.grey.shade300),
                      ),
                    ],
                  ),
                ],
              ),
            ),
            Column(
              children: [
                IconButton(
                  tooltip: 'Edit',
                  icon: const Icon(Icons.edit, color: Colors.amber),
                  onPressed: () => _tampilForm(item: item, index: index),
                ),
                IconButton(
                  tooltip: 'Hapus',
                  icon: const Icon(Icons.delete, color: Colors.redAccent),
                  onPressed: () => _hapusData(index),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Row(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(Icons.school, color: Colors.amber),
            SizedBox(width: 8),
            Text('Jadwal Pelajaran'),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: () => _tampilForm(),
        backgroundColor: Colors.amber,
        foregroundColor: Colors.black,
        icon: const Icon(Icons.add),
        label: const Text('Tambah'),
      ),
      body: Container(
        decoration: const BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
            colors: [Color(0xFF141414), Color(0xFF0B0B0B)],
          ),
        ),
        child: SafeArea(
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              children: [
                _buildHeader(),
                const SizedBox(height: 14),
                Row(
                  children: [
                    _buildStat('Total Jadwal', '${_data.length}', Icons.list_alt),
                    const SizedBox(width: 10),
                    _buildStat('Mapel Unik', '$_totalMapelUnik', Icons.book_outlined),
                  ],
                ),
                const SizedBox(height: 16),
                Expanded(
                  child: _data.isEmpty
                      ? _buildEmptyState()
                      : ListView.separated(
                          itemCount: _data.length,
                          separatorBuilder: (context, index) => const SizedBox(height: 12),
                          itemBuilder: (context, index) {
                            return _buildListItem(_data[index], index);
                          },
                        ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Hasilnya:




🎯 Kesimpulan

Aplikasi ini sudah mencakup dasar penting dalam Flutter:

  • CRUD (Create, Read, Update, Delete)

  • Validasi form

  • UI modern

  • Penggunaan widget dasar Flutter

Cocok digunakan sebagai tugas praktikum sekolah karena:

  • tidak terlalu kompleks

  • mudah dipahami

  • bisa dikembangkan lebih lanjut


By: Rizqi Resdhiana XI RPL II

Comments

Popular posts from this blog

Jenis-jenis sistem operasi os

Latihan membuat tampilan flutter sederhana 🤖

Belajar Flutter