✅ Artikel Lengkap: Aplikasi Absensi + Pengingat Jadwal dengan Flutter

 

📌 Pendahuluan

Di era digital, efisiensi adalah kunci. Bayangkan jika siswa atau karyawan harus mencatat absensi manual dan mengingat jadwal pelajaran atau meeting tanpa pengingat. Ribet, kan?

Nah, solusinya adalah aplikasi absensi berbasis digital yang terintegrasi dengan pengingat jadwal. Dengan satu aplikasi ini, kita bisa:
✔ Memantau kehadiran secara real-time.
✔ Menyimpan data absensi di cloud.
✔ Memberi pengingat jadwal otomatis agar tidak ada yang terlewat.

Dan asiknya, kita akan membuat ini menggunakan Flutter—framework UI yang multiplatform (Android, iOS, Web, bahkan Desktop).


💡 Manfaat Aplikasi Ini

Kenapa aplikasi ini penting? Berikut manfaatnya:

Untuk Siswa/Guru:

  • Siswa tidak lagi lupa pelajaran karena ada notifikasi pengingat.

  • Guru bisa mengecek absensi dengan cepat.

Untuk Perusahaan:

  • Monitoring kehadiran karyawan lebih efisien.

  • Pengingat meeting otomatis tanpa harus pakai chat manual.

Untuk Developer:

  • Belajar konsep CRUD (Create, Read, Update, Delete) dengan API.

  • Memahami notifikasi lokal & jadwal berbasis timezone.


🛠 Fitur Utama Aplikasi

CRUD Absensi menggunakan MockAPI.io sebagai backend.
Notifikasi pengingat jadwal pelajaran setiap harinya.
Tampilan daftar jadwal dengan highlight jika sedang berlangsung.
Form tambah/edit data absensi langsung dari aplikasi.


📸 Tampilan Aplikasi

Berikut tampilan dari aplikasi yang kita buat:





📥 Persiapan Proyek Flutter

Sebelum menulis kode, pastikan kamu sudah:

  1. Install Flutter SDK versi terbaru.

  2. Buat proyek baru:

    flutter create absensi_jadwal
  3. Edit pubspec.yaml untuk menambahkan dependency:

name: crud_mockapi description: A Flutter CRUD example with MockAPI.io publish_to: "none" version: 1.0.0+1 environment: sdk: ">=2.18.0 <4.0.0" dependencies: flutter: sdk: flutter http: ^0.13.6 intl: ^0.18.1 flutter_local_notifications: ^14.1.1 timezone: ^0.9.2 flutter: uses-material-design: true

Lalu jalankan:

flutter pub get

🧩 Kode Lengkap Aplikasi (main.dart)

Berikut adalah kode lengkapnya:

import 'package:flutter/material.dart';

import 'package:http/http.dart' as http;

import 'dart:convert';

import 'package:intl/intl.dart';

import 'package:intl/date_symbol_data_local.dart';

import 'package:flutter_local_notifications/flutter_local_notifications.dart';

import 'package:timezone/data/latest.dart' as tz;

import 'package:timezone/timezone.dart' as tz;


void main() async {

  WidgetsFlutterBinding.ensureInitialized();

  tz.initializeTimeZones();

  await initializeDateFormatting('id_ID', null);

  runApp(const MyApp());

}


class MyApp extends StatelessWidget {

  const MyApp({super.key});


  @override

  Widget build(BuildContext context) {

    return MaterialApp(

      title: 'Absensi + Jadwal Pengingat',

      theme: ThemeData(primarySwatch: Colors.blue),

      home: const HomePage(),

    );

  }

}


class HomePage extends StatefulWidget {

  const HomePage({super.key});


  @override

  State<HomePage> createState() => _HomePageState();

}


class _HomePageState extends State<HomePage> {

  final String apiUrl = "https://68a522582a3deed2960c7b4d.mockapi.io/absen";


  List items = [];


  final TextEditingController nameController = TextEditingController();

  final TextEditingController noAbsenController = TextEditingController();

  final TextEditingController statusController = TextEditingController();

  final TextEditingController keteranganController = TextEditingController();


  final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();


  @override

  void initState() {

    super.initState();

    fetchData();

    initNotifications();

    scheduleTodayNotifications();

  }


  Future<void> initNotifications() async {

    const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher');

    const InitializationSettings initializationSettings = InitializationSettings(android: initializationSettingsAndroid);

    await flutterLocalNotificationsPlugin.initialize(initializationSettings);

  }


  Future<void> showNotification(String title, String body) async {

    const AndroidNotificationDetails androidPlatformChannelSpecifics = AndroidNotificationDetails(

      'jadwal_channel',

      'Jadwal Pelajaran',

      importance: Importance.max,

      priority: Priority.high,

    );

    const NotificationDetails platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics);

    await flutterLocalNotificationsPlugin.show(0, title, body, platformChannelSpecifics);

  }


  Future<void> scheduleTodayNotifications() async {

    String hari = DateFormat('EEEE', 'id_ID').format(DateTime.now());

    if (jadwalPelajaran.containsKey(hari)) {

      for (var pelajaran in jadwalPelajaran[hari]!) {

        final startTime = pelajaran['start']!;

        final now = DateTime.now();

        final startParts = startTime.split(":");

        final notifTime = DateTime(now.year, now.month, now.day, int.parse(startParts[0]), int.parse(startParts[1]));


        if (notifTime.isAfter(now)) {

          await flutterLocalNotificationsPlugin.zonedSchedule(

            notifTime.millisecondsSinceEpoch ~/ 1000,

            'Pengingat Pelajaran',

            '${pelajaran['mapel']} dimulai sekarang!',

            tz.TZDateTime.from(notifTime, tz.local),

            const NotificationDetails(android: AndroidNotificationDetails('jadwal_channel', 'Jadwal Pelajaran')),

            androidAllowWhileIdle: true,

            uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime,

            matchDateTimeComponents: DateTimeComponents.time,

          );

        }

      }

    }

  }


  Future<void> fetchData() async {

    final response = await http.get(Uri.parse(apiUrl));

    if (response.statusCode == 200) {

      setState(() {

        items = json.decode(response.body);

      });

    }

  }


  Future<void> createData(String noAbsen, String name, String status, String keterangan) async {

    String tanggal = DateFormat('dd MMMM yyyy', 'id_ID').format(DateTime.now());

    await http.post(

      Uri.parse(apiUrl),

      headers: {"Content-Type": "application/json"},

      body: json.encode({

        "noAbsen": noAbsen,

        "name": name,

        "status": status,

        "keterangan": keterangan,

        "tanggal": tanggal,

      }),

    );

    fetchData();

  }


  Future<void> updateData(String id, String noAbsen, String name, String status, String keterangan) async {

    await http.put(

      Uri.parse("$apiUrl/$id"),

      headers: {"Content-Type": "application/json"},

      body: json.encode({

        "noAbsen": noAbsen,

        "name": name,

        "status": status,

        "keterangan": keterangan,

      }),

    );

    fetchData();

  }


  Future<void> deleteData(String id) async {

    await http.delete(Uri.parse("$apiUrl/$id"));

    fetchData();

  }


  void showForm({String? id, String? noAbsen, String? name, String? status, String? keterangan}) {

    if (id != null) {

      noAbsenController.text = noAbsen ?? "";

      nameController.text = name ?? "";

      statusController.text = status ?? "";

      keteranganController.text = keterangan ?? "";

    } else {

      noAbsenController.clear();

      nameController.clear();

      statusController.clear();

      keteranganController.clear();

    }


    showDialog(

      context: context,

      builder: (context) => AlertDialog(

        title: Text(id == null ? "Tambah Absen" : "Edit Absen"),

        content: SingleChildScrollView(

          child: Column(

            mainAxisSize: MainAxisSize.min,

            children: [

              TextField(

                controller: noAbsenController,

                decoration: const InputDecoration(labelText: "No Absen"),

                keyboardType: TextInputType.number,

              ),

              TextField(

                controller: nameController,

                decoration: const InputDecoration(labelText: "Nama"),

              ),

              TextField(

                controller: statusController,

                decoration: const InputDecoration(labelText: "Status (Hadir/Izin/Sakit/Terlambat)"),

              ),

              TextField(

                controller: keteranganController,

                decoration: const InputDecoration(labelText: "Keterangan"),

              ),

            ],

          ),

        ),

        actions: [

          TextButton(onPressed: () => Navigator.pop(context), child: const Text("Batal")),

          ElevatedButton(

            onPressed: () {

              if (id == null) {

                createData(noAbsenController.text, nameController.text, statusController.text, keteranganController.text);

              } else {

                updateData(id, noAbsenController.text, nameController.text, statusController.text, keteranganController.text);

              }

              Navigator.pop(context);

            },

            child: const Text("Simpan"),

          ),

        ],

      ),

    );

  }


  @override

  Widget build(BuildContext context) {

    String hari = DateFormat('EEEE', 'id_ID').format(DateTime.now());

    List<Map<String, String>> jadwalHariIni = jadwalPelajaran[hari] ?? [];


    String nowTime = DateFormat('HH:mm').format(DateTime.now());


    return Scaffold(

      appBar: AppBar(title: const Text("Absensi + Pengingat Jadwal")),

      body: Column(

        children: [

          Container(

            color: Colors.blue.shade50,

            padding: const EdgeInsets.all(10),

            child: Column(

              crossAxisAlignment: CrossAxisAlignment.start,

              children: [

                Text("Hari ini: $hari", style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),

                const SizedBox(height: 5),

                ...jadwalHariIni.map((pelajaran) {

                  bool isNow = nowTime.compareTo(pelajaran['start']!) >= 0 && nowTime.compareTo(pelajaran['end']!) <= 0;

                  return Text(

                    "${pelajaran['mapel']} (${pelajaran['start']} - ${pelajaran['end']})",

                    style: TextStyle(color: isNow ? Colors.green : Colors.black, fontWeight: isNow ? FontWeight.bold : FontWeight.normal),

                  );

                }).toList(),

              ],

            ),

          ),

          Expanded(

            child: items.isEmpty

                ? const Center(child: CircularProgressIndicator())

                : ListView.builder(

                    itemCount: items.length,

                    itemBuilder: (context, index) {

                      final item = items[index];

                      return Card(

                        child: ListTile(

                          title: Text("${item["noAbsen"]}. ${item["name"]}"),

                          subtitle: Text("${item["status"]} | ${item["keterangan"]} | ${item["tanggal"]}"),

                          trailing: Row(

                            mainAxisSize: MainAxisSize.min,

                            children: [

                              IconButton(

                                icon: const Icon(Icons.edit, color: Colors.orange),

                                onPressed: () => showForm(

                                  id: item["id"],

                                  noAbsen: item["noAbsen"],

                                  name: item["name"],

                                  status: item["status"],

                                  keterangan: item["keterangan"],

                                ),

                              ),

                              IconButton(

                                icon: const Icon(Icons.delete, color: Colors.red),

                                onPressed: () => deleteData(item["id"]),

                              ),

                            ],

                          ),

                        ),

                      );

                    },

                  ),

          ),

        ],

      ),

      floatingActionButton: FloatingActionButton(

        onPressed: () => showForm(),

        child: const Icon(Icons.add),

      ),

    );

  }

}


final Map<String, List<Map<String, String>>> jadwalPelajaran = {

  "Senin": [

    {"mapel": "KK ProgTeksMul", "start": "07:15", "end": "08:35"},

    {"mapel": "B.Ingg", "start": "09:10", "end": "10:30"},

    {"mapel": "KK ProgWebBSdt", "start": "10:35", "end": "11:45"},

  ],

  "Selasa": [

    {"mapel": "Pend.Agama", "start": "07:15", "end": "08:35"},

    {"mapel": "Pend.Panc", "start": "08:40", "end": "09:20"},

    {"mapel": "BP/BK", "start": "09:30", "end": "10:00"},

    {"mapel": "MP Pem.Gim", "start": "10:15", "end": "11:45"},

  ],

  "Rabu": [

    {"mapel": "KK ProgMbl", "start": "07:15", "end": "09:30"},

    {"mapel": "B.Sun", "start": "09:35", "end": "11:45"},

  ],

  "Kamis": [

    {"mapel": "B.Ingg", "start": "07:15", "end": "08:35"},

    {"mapel": "KIK", "start": "08:40", "end": "10:00"},

    {"mapel": "B.Indo", "start": "10:15", "end": "11:45"},

  ],

  "Jumat": [

    {"mapel": "Mtk", "start": "07:15", "end": "08:35"},

    {"mapel": "KK ProgWebBSdt", "start": "08:40", "end": "10:45"},

  ],

};



🔗 Link Demo Aplikasi

Kamu bisa coba langsung di browser menggunakan Zapp.run di link berikut:
[https://z95006929510.zapp.page/#/]


🚀 Cara Menjalankan Aplikasi

  1. Clone proyek atau buat sendiri pakai kode di atas.

  2. Jalankan perintah:

    flutter run
  3. Untuk web:

    flutter run -d chrome

📌 Kesimpulan & Ide Pengembangan

Dengan aplikasi ini, kita berhasil membuat:
✅ CRUD absensi (Create, Read, Update, Delete)
✅ Pengingat jadwal berbasis notifikasi lokal
✅ Tampilan jadwal dengan highlight pelajaran yang sedang berlangsung

Ide pengembangan berikutnya:

  • Tambah login & autentikasi untuk keamanan data.

  • Simpan data ke Firebase agar real-time.

  • Integrasi calendar view untuk jadwal mingguan.


Penutup

Dengan tutorial ini, kamu sudah punya aplikasi yang powerful untuk kebutuhan sekolah, kampus, atau bahkan perusahaan. Tinggal modifikasi sesuai kebutuhan.

🔥 Selamat mencoba dan happy coding! 🔥

Komentar

Postingan populer dari blog ini

✨ Statement dan Expression dalam Dart (Flutter)