membuat aplikasi sederhana dengan konsep CRUD - ngawiflix

= CRUD adalah singkatan dari create,read,update,delete...

        project ini dibuat sepenuhnya oleh dhanis fathan gunawan yang ditemani oleh saya (caca ardiansyah) dan dibantu oleh ChatGPT.


Membuat Aplikasi Jadwal Bioskop dengan Flutter

berfungsi untuk menampilkan, menambah, mengubah, dan menghapus daftar film beserta jadwal tayangnya.


(hasil akhir)




langsung aja ini codenya :

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    debugShowCheckedModeBanner: false,
    home: JadwalBioskop(),
  ));
}

class JadwalBioskop extends StatefulWidget {
  @override
  State<JadwalBioskop> createState() => _JadwalBioskopState();
}

class _JadwalBioskopState extends State<JadwalBioskop> {
  List<Map<String, String>> filmList = [
    {
      "judul": "Ambaruwo",
      "genre": "Horror, Sci-Fi",
      "jam": "14:14, 17:17, 00:00",
      "poster": "https://p16-sign-va.tiktokcdn.com/tos-maliva-i-photomode-us/2c1bf404b3494dbe88d2cfb529a07d8f~tplv-photomode-image-v1:q70.webp?dr=1334&refresh_token=96c9dbee&x-expires=1756436400&x-signature=hy0B0aIA8wrqS8GHYZ%2Fc5d2My98%3D&t=5897f7ec&ps=b40d0ec8&shp=d05b14bd&shcp=1d1a97fc&idc=my&s=AWEME_DETAIL&biz_tag=tt_photomode&sc=image",
      "deskripsi":
          "Si Hytam kembali bangkit untuk menghytamkan kalian! Saksikan aksi seru ini di"
    },
    {
      "judul": "Ambarawuhi",
      "genre": "Horror, Adventure",
      "jam": "10:00, 13:00, 16:00",
      "poster":
          "https://p16-sign-va.tiktokcdn.com/tos-maliva-i-photomode-us/0159708740824b7395ae7b79c0ccf029~tplv-photomode-image-v1:q70.webp?dr=1334&refresh_token=58793e05&x-expires=1756436400&x-signature=7N62%2BwV6uxoottmwKwsBMBU2cF8%3D&t=5897f7ec&ps=b40d0ec8&shp=d05b14bd&shcp=1d1a97fc&idc=sg1&s=AWEME_DETAIL&biz_tag=tt_photomode&sc=image",
      "deskripsi":
          "Sekelompok KKN dari kota Ngawi yang diterror karena melakukan hal terlarang..."
    },
    {
      "judul": "AMBATUKAM: one for all",
      "genre": "Adventure",
      "jam": "10:00, 13:00, 16:00",
      "poster":
          "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSoTKE46hTPb_M8oWrv8yX7YRRAWYPOCRDEOQ&s",
      "deskripsi": "Petualangan seru melawan desa misterius..."
    },
    {
      "judul": "imut adalah maut",
      "genre": "Romance, Drama",
      "jam": "10:00, 13:00, 16:00",
      "poster":
          "https://p16-sign-va.tiktokcdn.com/tos-maliva-i-photomode-us/fff9a32b4474448482aea36881835dce~tplv-photomode-image-v1:q70.webp?dr=1334&refresh_token=6ec2b866&x-expires=1756436400&x-signature=u13SV9baoxGbHOa2UbHGp3w94kQ%3D&t=5897f7ec&ps=b40d0ec8&shp=d05b14bd&shcp=1d1a97fc&idc=my&s=AWEME_DETAIL&biz_tag=tt_photomode&sc=image",
      "deskripsi": "drama rumah tangga yang diganggu oleh siimut"
    },
    {
      "judul": "Anggrek mekar berduri",
      "genre": "Drama",
      "jam": "-",
      "poster":
          "https://p16-sign-va.tiktokcdn.com/tos-maliva-i-photomode-us/bee02b17c3aa485fb23c8d42359dca9d~tplv-photomode-image-v1:q70.webp?dr=1334&refresh_token=d5dc6895&x-expires=1756436400&x-signature=G3NJ9I%2BrORo%2FaEFoEY%2BLdQ56CJA%3D&t=5897f7ec&ps=b40d0ec8&shp=d05b14bd&shcp=1d1a97fc&idc=my&s=AWEME_DETAIL&biz_tag=tt_photomode&sc=image",
      "deskripsi": "-"
    },
    {
      "judul": "Ambabelle",
      "genre": "Horror, shounen",
      "jam": "-",
      "poster":
          "https://p16-sign-va.tiktokcdn.com/tos-maliva-i-photomode-us/033b71378e464c6ab3d974eb1b20cd86~tplv-photomode-image-v1:q70.webp?dr=1334&refresh_token=3164494f&x-expires=1756436400&x-signature=3UVFVwpJDuyDBbnSOdhSmWZIUAA%3D&t=5897f7ec&ps=b40d0ec8&shp=d05b14bd&shcp=1d1a97fc&idc=my&s=AWEME_DETAIL&biz_tag=tt_photomode&sc=image",
      "deskripsi": "Film horror luar yang dibuat ulang di ngawi city"
    },
  ];

  String searchQuery = "";

  void tambahFilm() {
    showFormDialog();
  }

  void ubahFilm(int index) {
    var film = filmList[index];
    showFormDialog(editIndex: index, film: film);
  }

  void showFormDialog({int? editIndex, Map<String, String>? film}) {
    String judul = film?["judul"] ?? "";
    String genre = film?["genre"] ?? "";
    String jam = film?["jam"] ?? "";
    String poster = film?["poster"] ?? "";
    String deskripsi = film?["deskripsi"] ?? "";

    showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          backgroundColor: Colors.grey[900],
          title: Text(
            editIndex == null ? "Tambah Film" : "Ubah Film",
            style: const TextStyle(color: Colors.white),
          ),
          content: SingleChildScrollView(
            child: Column(
              children: [
                buildInput("Judul", (val) => judul = val, judul),
                buildInput("Genre", (val) => genre = val, genre),
                buildInput("Jam Tayang", (val) => jam = val, jam),
                buildInput("URL Poster", (val) => poster = val, poster),
                buildInput("Deskripsi", (val) => deskripsi = val, deskripsi,
                    maxLines: 3),
              ],
            ),
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context),
              child: const Text("Batal", style: TextStyle(color: Colors.red)),
            ),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  if (editIndex == null) {
                    filmList.add({
                      "judul": judul,
                      "genre": genre,
                      "jam": jam,
                      "poster": poster,
                      "deskripsi": deskripsi,
                    });
                  } else {
                    filmList[editIndex] = {
                      "judul": judul,
                      "genre": genre,
                      "jam": jam,
                      "poster": poster,
                      "deskripsi": deskripsi,
                    };
                  }
                });
                Navigator.pop(context);
              },
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.red[900],
              ),
              child: const Text("Simpan",
                  style: TextStyle(color: Colors.white)),
            ),
          ],
        );
      },
    );
  }

  static Widget buildInput(
      String label, Function(String) onChanged, String initialValue,
      {int maxLines = 1}) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4),
      child: TextField(
        controller: TextEditingController(text: initialValue),
        style: const TextStyle(color: Colors.white),
        onChanged: onChanged,
        maxLines: maxLines,
        decoration: InputDecoration(
          labelText: label,
          labelStyle: const TextStyle(color: Colors.white),
          filled: true,
          fillColor: Colors.grey[800],
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(8),
            borderSide: BorderSide.none,
          ),
        ),
      ),
    );
  }

  void hapusFilm(int index) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        backgroundColor: Colors.grey[900],
        title: const Text("Konfirmasi",
            style: TextStyle(color: Colors.white)),
        content: const Text("Yakin ingin menghapus film ini?",
            style: TextStyle(color: Colors.white70)),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child:
                const Text("Batal", style: TextStyle(color: Colors.grey)),
          ),
          ElevatedButton(
            style: ElevatedButton.styleFrom(
              backgroundColor: Colors.red[900],
            ),
            onPressed: () {
              setState(() {
                filmList.removeAt(index);
              });
              Navigator.pop(context);
            },
            child:
                const Text("Hapus", style: TextStyle(color: Colors.white)),
          ),
        ],
      ),
    );
  }

  void detailFilm(Map<String, String> film) {
    showDialog(
      context: context,
      builder: (context) {
        final size = MediaQuery.of(context).size;
        return Dialog(
          backgroundColor: Colors.grey[900],
          shape:
              RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
          child: ConstrainedBox(
            constraints: BoxConstraints(
              maxWidth: 520,
              maxHeight: size.height * 0.85,
            ),
            child: SingleChildScrollView(
              child: Column(
                children: [
                  ClipRRect(
                    borderRadius:
                        const BorderRadius.vertical(top: Radius.circular(12)),
                    child: AspectRatio(
                      aspectRatio: 2 / 3,
                      child: Image.network(film["poster"]!,
                          fit: BoxFit.cover),
                    ),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(12),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(film["judul"]!,
                            style: const TextStyle(
                                fontSize: 22,
                                fontWeight: FontWeight.bold,
                                color: Colors.white)),
                        const SizedBox(height: 4),
                        Text(film["genre"]!,
                            style: const TextStyle(color: Colors.grey)),
                        const SizedBox(height: 8),
                        Text("Jam: ${film["jam"]}",
                            style:
                                const TextStyle(color: Colors.white70)),
                        const SizedBox(height: 12),
                        Text(film["deskripsi"]!,
                            style:
                                const TextStyle(color: Colors.white)),
                        const SizedBox(height: 16),
                        Align(
                          alignment: Alignment.centerRight,
                          child: ElevatedButton(
                            style: ElevatedButton.styleFrom(
                                backgroundColor: Colors.red[900]),
                            onPressed: () => Navigator.pop(context),
                            child: const Text("Keluar",
                                style: TextStyle(color: Colors.white)),
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    final width = MediaQuery.of(context).size.width;

    List<Map<String, String>> filteredList = filmList
        .where((film) => film["judul"]!
            .toLowerCase()
            .contains(searchQuery.toLowerCase()))
        .toList();

    return Scaffold(
      backgroundColor: Colors.black,
      appBar: AppBar(
        title: const Text("Ngawiflix - Jadwal Bioskop",
            style: TextStyle(fontWeight: FontWeight.bold)),
        backgroundColor: Colors.red[900],
        actions: [
          IconButton(
            onPressed: tambahFilm,
            icon: const Icon(Icons.add, color: Colors.white),
          ),
        ],
      ),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: TextField(
              onChanged: (value) => setState(() => searchQuery = value),
              style: const TextStyle(color: Colors.white),
              decoration: InputDecoration(
                hintText: "Cari judul film...",
                hintStyle: const TextStyle(color: Colors.grey),
                prefixIcon: const Icon(Icons.search, color: Colors.white),
                filled: true,
                fillColor: Colors.grey[900],
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(8),
                  borderSide: BorderSide.none,
                ),
              ),
            ),
          ),
          Expanded(
            child: GridView.builder(
              padding: const EdgeInsets.all(12),
              gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
                maxCrossAxisExtent: width < 420 ? 360 : 240,
                crossAxisSpacing: 12,
                mainAxisSpacing: 12,
                childAspectRatio: 0.55,
              ),
              itemCount: filteredList.length,
              itemBuilder: (context, index) {
                var film = filteredList[index];
                return Card(
                  color: Colors.grey[900],
                  shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(12)),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Expanded(
                        child: InkWell(
                          onTap: () => detailFilm(film),
                          child: ClipRRect(
                            borderRadius: const BorderRadius.vertical(
                                top: Radius.circular(12)),
                            child: Image.network(
                              film["poster"]!,
                              fit: BoxFit.cover,
                              width: double.infinity,
                              errorBuilder: (_, __, ___) => const Center(
                                  child: Icon(Icons.broken_image)),
                            ),
                          ),
                        ),
                      ),
                      Padding(
                        padding: const EdgeInsets.all(8),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(film["judul"]!,
                                style: const TextStyle(
                                    fontSize: 16,
                                    fontWeight: FontWeight.bold,
                                    color: Colors.white),
                                maxLines: 2,
                                overflow: TextOverflow.ellipsis),
                            const SizedBox(height: 4),
                            Text(film["genre"]!,
                                style: const TextStyle(
                                    color: Colors.grey, fontSize: 12),
                                maxLines: 1,
                                overflow: TextOverflow.ellipsis),
                            const SizedBox(height: 4),
                            Text("Jam: ${film["jam"]}",
                                style: const TextStyle(
                                    color: Colors.white70, fontSize: 12),
                                maxLines: 1,
                                overflow: TextOverflow.ellipsis),
                            const SizedBox(height: 8),
                            Row(
                              mainAxisAlignment:
                                  MainAxisAlignment.spaceBetween,
                              children: [
                                IconButton(
                                  icon: const Icon(Icons.edit,
                                      color: Colors.yellow),
                                  onPressed: () => ubahFilm(index),
                                ),
                                IconButton(
                                  icon: const Icon(Icons.delete,
                                      color: Colors.red),
                                  onPressed: () => hapusFilm(index),
                                ),
                              ],
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

Fitur Aplikasi

  1. Menampilkan daftar film dalam bentuk.

  2. Mencari film berdasarkan judul.

  3. Menambah data film baru.

  4. Mengubah data film yang ada.

  5. Menghapus data film.

  6. Menampilkan detail film.

^ ^ ^ ^ ^ 
___________________________________________________________________________________

dhanis fathan gunawan (main)
caca ardiansyah

ChatGPT & Google

Komentar

Postingan populer dari blog ini

Belajar Flutter Biar Gak Cupu: Bikin App Ada Foto + Tombol SnackBar

Membuat Aplikasi Flutter Daftar Film MCU dengan Navigasi & Layout responsif