"Tutorial Flutter untuk Pemula: Dari Ide Jastip Kantin ke Aplikasi WhatsApp"

 

🎯 Pendahuluan

Jajan di kantin itu urusan serius. Apalagi saat jam istirahat — antrean panjang, waktu mepet, belum lagi rebutan kursi. Bayangkan kalau ada satu orang yang ditugaskan jastip (jasa titip), tapi semua pesanan harus dicatat manual lewat chat WhatsApp yang berantakan.

Nah, di praktikum kali ini, kita akan membangun aplikasi Flutter untuk mengelola jastip jajan kantin — dari memilih warung, memilih menu, memasukkan ke keranjang, sampai mengirim pesanan terformat ke WhatsApp jastip.

Yang menarik: semua ini kita kerjakan dalam 1 pertemuan (3 jam) dengan bantuan AI sebagai co-pilot coding.


🎓 Tujuan Praktikum

Setelah menyelesaikan praktikum ini, siswa diharapkan mampu:

  1. Memahami alur aplikasi nyata dari sisi user flow, bukan cuma "belajar widget"

  2. Menerapkan navigasi antar halaman di Flutter

  3. Mengelola state sederhana (keranjang) dengan setState

  4. Melakukan integrasi eksternal dengan url_launcher ke WhatsApp

  5. Memanfaatkan AI sebagai asisten belajar, bukan sebagai mesin copy-paste


🗺️ Alur Aplikasi

Sebelum ngoding, pahami dulu alurnya:

text
[Pilih Warung] → [Lihat Menu Warung] → [Tambah ke Keranjang]
                                              ↓
[Kirim ke WA Jastip] ← [Isi Data & Checkout] ← [Lihat Keranjang]

Struktur data sederhana:

text
Warung → Menu (nama, harga) → Keranjang (menu + qty) → Order → WhatsApp

Prinsip yang dipegang di praktikum ini: alur jalan dulu, polish belakangan. Tidak ada database, tidak ada login, tidak ada riwayat pesanan. Fokus ke alur inti.


📁 Struktur Folder Projek

text
lib/
├── main.dart              // Entry point + state keranjang
├── models.dart            // Warung, Menu, CartItem
├── data_dummy.dart        // Data warung & menu
└── screens/
    ├── home_screen.dart   // Daftar warung
    ├── menu_screen.dart   // Menu per warung
    └── cart_screen.dart   // Keranjang + checkout WA

Kenapa sesederhana ini? Supaya siswa bisa fokus ke alur, bukan terjebak konfigurasi folder.


⚙️ Persiapan

1. Buat projek baru:

bash
flutter create jastip_kantin
cd jastip_kantin

2. Tambahkan dependency di pubspec.yaml:

yaml
dependencies:
  flutter:
    sdk: flutter
  url_launcher: ^6.2.5

3. Jalankan:

bash
flutter pub get

🧩 Bagian 1 — Models & Data Dummy

Buat file lib/models.dart:

dart
class Warung {
  final String nama;
  final String emoji;
  Warung({required this.nama, required this.emoji});
}

class Menu {
  final String nama;
  final int harga;
  final String warungNama;
  Menu({required this.nama, required this.harga, required this.warungNama});
}

class CartItem {
  final Menu menu;
  int qty;
  CartItem({required this.menu, this.qty = 1});
  int get subtotal => menu.harga * qty;
}

Buat file lib/data_dummy.dart:

dart
import 'models.dart';

final List<Warung> daftarWarung = [
  Warung(nama: 'Warung Bu Siti', emoji: '🍜'),
  Warung(nama: 'Kantin Pak Budi', emoji: '🍛'),
  Warung(nama: 'Kantin Sehat', emoji: '🥗'),
];

final List<Menu> daftarMenu = [
  Menu(nama: 'Indomie Goreng', harga: 8000, warungNama: 'Warung Bu Siti'),
  Menu(nama: 'Es Teh', harga: 3000, warungNama: 'Warung Bu Siti'),
  Menu(nama: 'Nasi Ayam', harga: 12000, warungNama: 'Kantin Pak Budi'),
  Menu(nama: 'Nasi Telur', harga: 10000, warungNama: 'Kantin Pak Budi'),
  Menu(nama: 'Salad Buah', harga: 15000, warungNama: 'Kantin Sehat'),
  Menu(nama: 'Jus Alpukat', harga: 10000, warungNama: 'Kantin Sehat'),
];

💡 Catatan untuk siswa: Perhatikan bahwa CartItem.qty tidak final karena nilainya berubah saat user menambah/mengurangi jumlah. Sedangkan Menu.harga final karena tidak akan berubah.


🧩 Bagian 2 — State Keranjang di Root

Buka lib/main.dart, kita taruh state keranjang di root widget supaya bisa diakses dari semua halaman:

dart
import 'package:flutter/material.dart';
import 'screens/home_screen.dart';
import 'models.dart';

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

class MyApp extends StatefulWidget {
  const MyApp({super.key});
  
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final List<CartItem> keranjang = [];

  void tambahKeKeranjang(Menu menu) {
    setState(() {
      final idx = keranjang.indexWhere((c) => c.menu.nama == menu.nama);
      if (idx >= 0) {
        keranjang[idx].qty++;
      } else {
        keranjang.add(CartItem(menu: menu));
      }
    });
  }

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Jastip Kantin',
      theme: ThemeData(primarySwatch: Colors.orange),
      home: HomeScreen(
        keranjang: keranjang,
        onTambah: tambahKeKeranjang,
      ),
    );
  }
}

💡 Diskusi: Kenapa state keranjang ditaruh di MyApp, bukan di CartScreen? Karena keranjang harus tetap hidup saat user berpindah halaman. Kalau ditaruh di CartScreen, begitu user keluar dari halaman itu, keranjangnya hilang.


🧩 Bagian 3 — Halaman Daftar Warung

Buat lib/screens/home_screen.dart:

dart
import 'package:flutter/material.dart';
import '../models.dart';
import '../data_dummy.dart';
import 'menu_screen.dart';
import 'cart_screen.dart';

class HomeScreen extends StatelessWidget {
  final List<CartItem> keranjang;
  final Function(Menu) onTambah;

  const HomeScreen({super.key, required this.keranjang, required this.onTambah});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Jastip Kantin'),
        actions: [
          IconButton(
            icon: Badge(
              label: Text('${keranjang.fold(0, (s, c) => s + c.qty)}'),
              isLabelVisible: keranjang.isNotEmpty,
              child: const Icon(Icons.shopping_cart),
            ),
            onPressed: () => Navigator.push(context, MaterialPageRoute(
              builder: (_) => CartScreen(keranjang: keranjang),
            )),
          ),
        ],
      ),
      body: ListView.builder(
        itemCount: daftarWarung.length,
        itemBuilder: (context, i) {
          final w = daftarWarung[i];
          return Card(
            margin: const EdgeInsets.all(8),
            child: ListTile(
              leading: Text(w.emoji, style: const TextStyle(fontSize: 32)),
              title: Text(w.nama),
              trailing: const Icon(Icons.chevron_right),
              onTap: () => Navigator.push(context, MaterialPageRoute(
                builder: (_) => MenuScreen(
                  warung: w,
                  keranjang: keranjang,
                  onTambah: onTambah,
                ),
              )),
            ),
          );
        },
      ),
    );
  }
}

🧩 Bagian 4 — Halaman Menu Warung

Buat lib/screens/menu_screen.dart:

dart
import 'package:flutter/material.dart';
import '../models.dart';
import '../data_dummy.dart';

class MenuScreen extends StatelessWidget {
  final Warung warung;
  final List<CartItem> keranjang;
  final Function(Menu) onTambah;

  const MenuScreen({
    super.key,
    required this.warung,
    required this.keranjang,
    required this.onTambah,
  });

  
  Widget build(BuildContext context) {
    final menuWarung = daftarMenu.where((m) => m.warungNama == warung.nama).toList();

    return Scaffold(
      appBar: AppBar(title: Text('${warung.emoji} ${warung.nama}')),
      body: ListView.builder(
        itemCount: menuWarung.length,
        itemBuilder: (context, i) {
          final m = menuWarung[i];
          return ListTile(
            title: Text(m.nama),
            subtitle: Text('Rp ${m.harga}'),
            trailing: IconButton(
              icon: const Icon(Icons.add_circle, color: Colors.orange),
              onPressed: () {
                onTambah(m);
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(
                    content: Text('${m.nama} ditambahkan'),
                    duration: const Duration(seconds: 1),
                  ),
                );
              },
            ),
          );
        },
      ),
    );
  }
}

🧩 Bagian 5 — Halaman Keranjang & Checkout WA

Buat lib/screens/cart_screen.dart:

dart
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import '../models.dart';

class CartScreen extends StatefulWidget {
  final List<CartItem> keranjang;
  const CartScreen({super.key, required this.keranjang});

  
  State<CartScreen> createState() => _CartScreenState();
}

class _CartScreenState extends State<CartScreen> {
  final namaCtrl = TextEditingController();
  final kelasCtrl = TextEditingController();
  final catatanCtrl = TextEditingController();
  final String nomorJastip = '6281234567890'; // Ganti dengan nomor WA jastip

  int get total => widget.keranjang.fold(0, (s, c) => s + c.subtotal);

  Future<void> kirimWA() async {
    if (widget.keranjang.isEmpty) return;
    if (namaCtrl.text.isEmpty || kelasCtrl.text.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Nama & kelas wajib diisi')),
      );
      return;
    }

    final pesan = '''
Halo Kak Jastip! 🛒
Nama: ${namaCtrl.text}
Kelas: ${kelasCtrl.text}

Pesanan:
${widget.keranjang.map((c) => '- ${c.menu.nama} x${c.qty} = Rp${c.subtotal}').join('\n')}

Total: Rp$total
Catatan: ${catatanCtrl.text}
''';

    final url = Uri.parse(
      'https://wa.me/$nomorJastip?text=${Uri.encodeComponent(pesan)}',
    );

    if (await canLaunchUrl(url)) {
      await launchUrl(url, mode: LaunchMode.externalApplication);
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Keranjang')),
      body: widget.keranjang.isEmpty
          ? const Center(child: Text('Keranjang kosong 🛒'))
          : Column(
              children: [
                Expanded(
                  child: ListView.builder(
                    itemCount: widget.keranjang.length,
                    itemBuilder: (context, i) {
                      final c = widget.keranjang[i];
                      return ListTile(
                        title: Text(c.menu.nama),
                        subtitle: Text('Rp ${c.menu.harga}'),
                        trailing: Row(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            IconButton(
                              icon: const Icon(Icons.remove),
                              onPressed: () => setState(() {
                                if (c.qty > 1) {
                                  c.qty--;
                                } else {
                                  widget.keranjang.removeAt(i);
                                }
                              }),
                            ),
                            Text('${c.qty}'),
                            IconButton(
                              icon: const Icon(Icons.add),
                              onPressed: () => setState(() => c.qty++),
                            ),
                          ],
                        ),
                      );
                    },
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    children: [
                      TextField(
                        controller: namaCtrl,
                        decoration: const InputDecoration(labelText: 'Nama'),
                      ),
                      TextField(
                        controller: kelasCtrl,
                        decoration: const InputDecoration(labelText: 'Kelas'),
                      ),
                      TextField(
                        controller: catatanCtrl,
                        decoration: const InputDecoration(
                          labelText: 'Catatan (opsional)',
                        ),
                      ),
                      const SizedBox(height: 12),
                      Text(
                        'Total: Rp $total',
                        style: const TextStyle(
                          fontSize: 20,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(height: 12),
                      SizedBox(
                        width: double.infinity,
                        child: ElevatedButton.icon(
                          icon: const Icon(Icons.send),
                          label: const Text('Kirim ke WA Jastip'),
                          onPressed: kirimWA,
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
    );
  }
}

⚠️ Penting: Ganti nomorJastip dengan nomor WhatsApp asli jastip (format 62... tanpa + dan tanpa 0 di depan).


✅ Uji Coba Aplikasi

Setelah semua file selesai, jalankan:

bash
flutter run

Cek alur lengkap:

  1. Pilih Warung Bu Siti → tambah Indomie Goreng + Es Teh

  2. Kembali, pilih Kantin Pak Budi → tambah Nasi Ayam

  3. Tap ikon keranjang di kanan atas → lihat badge jumlah item

  4. Isi nama, kelas, catatan → tap Kirim ke WA Jastip

  5. WhatsApp terbuka dengan pesan terformat rapi

Kalau semua jalan — selamat, aplikasimu sudah live! 🎉


🧠 Refleksi Belajar

Coba jawab pertanyaan berikut untuk mengukur pemahaman:

  1. Kenapa state keranjang ditaruh di MyApp, bukan di CartScreen?

  2. Kenapa CartScreen pakai StatefulWidget sedangkan HomeScreen StatelessWidget?

  3. Apa fungsi Uri.encodeComponent() pada URL WhatsApp?

  4. Bagaimana cara menambah fitur "pilih beberapa warung sekaligus dalam 1 pesanan"?

  5. Apa yang perlu diubah kalau ingin menambah warung ke-4?


🚀 Tantangan Lanjutan

Untuk yang sudah selesai lebih cepat, coba tambahkan:

  • Level 1 (+5 menit): Tombol hapus item di keranjang

  • Level 2 (+10 menit): Format harga jadi Rp 8.000 pakai package intl

  • Level 3 (+15 menit): Fitur pencarian menu di MenuScreen

  • Level 4 (bonus): Simpan riwayat pesanan pakai shared_preferences


💡 Tips Belajar dengan AI

AI itu co-pilot, bukan autopilot. Pola prompt yang sehat:

✅ Prompt Baik❌ Prompt Kurang Baik
"Jelaskan konsep setState sebelum aku pakai""Buatkan aplikasi jastip lengkap"
"Error ini artinya apa dan kenapa muncul?""Fix kode ini"
"Tunjukkan 2 cara berbeda untuk passing data""Bikin yang paling bagus"
"Refactor kode ini biar lebih rapi""Ganti semua jadi clean architecture"

Aturan main:

  • Ketik sendiri, jangan copy-paste

  • Harus bisa jelaskan setiap barisnya

  • Test dulu sebelum lanjut

  • Jangan minta AI buatkan 1 projek penuh sekaligus


📋 Checklist Praktikum

Centang kalau sudah selesai:

  • □

    Projek Flutter dibuat & url_launcher terinstall

  • □

    3 model (Warung, Menu, CartItem) dibuat

  • □

    Data dummy warung & menu terisi

  • □

    Halaman daftar warung tampil

  • □

    Halaman menu per warung tampil

  • □

    Tombol tambah berfungsi & badge update

  • □

    Halaman keranjang tampil dengan qty +/-

  • □

    Tombol kirim ke WA membuka WhatsApp

  • □

    Pesan WhatsApp terformat rapi


🏁 Penutup

Selamat! Dalam 3 jam, kamu sudah membangun aplikasi Flutter nyata dengan alur lengkap: dari memilih warung, memilih menu, mengelola keranjang, sampai integrasi ke WhatsApp.

Yang lebih penting dari aplikasinya: kamu sudah belajar berpikir dalam alur aplikasi, bukan cuma menumpuk widget. Skill ini yang akan kamu pakai di projek-projek berikutnya.

Tugas Akhir Praktikum:

📝 Tulis postingan blog pribadi yang berisi:

  1. Screenshot aplikasi yang sudah kamu buat (minimal 3: home, menu, cart, WA)

  2. Penjelasan singkat alur aplikasi versimu

  3. Kendala yang kamu hadapi + cara mengatasinya (boleh karena bantuan AI)

  4. Refleksi: apa hal baru yang kamu pahami tentang Flutter?

  5. Link postinganmu, tempel di kolom komentar postingan ini 👇

Selamat berkarya, calon developer! 🚀

📝 Template Postingan Blog untuk Siswa

Petunjuk untuk siswa: Copy template di bawah ini ke blog kalian (WordPress/Medium/Hashnode/Blogger), lalu ganti semua bagian yang ada tanda [...] dengan isi kalian sendiri. Hapus bagian petunjuk yang tidak perlu.


📋 TEMPLATE — Mulai Copy dari Sini


Praktikum Flutter: Aplikasi Jastip Jajan Kantin — [Nama Kalian]

Nama: [Nama Lengkap]
Kelas: [Kelas]
Tanggal Praktikum: [DD MMMM YYYY]
Link Repo GitHub: [URL repo kalian, jika ada]


🎯 Pendahuluan

Pada praktikum kali ini, saya membuat aplikasi Jastip Jajan Kantin menggunakan Flutter. Aplikasi ini bertujuan untuk mempermudah proses pemesanan jajan di kantin melalui jasa titip (jastip), mulai dari memilih warung, memilih menu, mengelola keranjang, hingga mengirim pesanan ke WhatsApp jastip.

Alasan saya memilih/mengerjakan projek ini: [tulis 2-3 kalimat — misal: karena ingin belajar alur aplikasi nyata, atau karena tertarik dengan integrasi WhatsApp]


🗺️ Alur Aplikasi Versi Saya

[Gambarkan alur aplikasi yang kalian buat, bisa berupa teks atau diagram sederhana]

text
[Pilih Warung] → [Lihat Menu] → [Tambah ke Keranjang] → [Checkout] → [Kirim ke WA]

Penjelasan singkat alur:

  1. [Langkah 1 — misal: User membuka aplikasi dan melihat daftar warung]

  2. [Langkah 2 — ...]

  3. [Langkah 3 — ...]

  4. [Langkah 4 — ...]

  5. [Langkah 5 — ...]


🛠️ Teknologi & Tools yang Digunakan

  • Framework: Flutter [versi]

  • Bahasa: Dart

  • Package tambahan: [url_launcher / intl / dll]

  • Editor: [VS Code / Android Studio]

  • Bantuan AI: [ChatGPT / Claude / Gemini / lainnya]


📸 Screenshot Aplikasi

Wajib: Minimal 4 screenshot — home, menu, keranjang, dan WhatsApp terbuka.

1. Halaman Daftar Warung

![Screenshot Home]([URL gambar kalian])
Deskripsi: [jelaskan singkat apa yang tampil di halaman ini]

2. Halaman Menu Warung

![Screenshot Menu]([URL gambar kalian])
Deskripsi: [jelaskan singkat]

3. Halaman Keranjang

![Screenshot Keranjang]([URL gambar kalian])
Deskripsi: [jelaskan singkat]

4. Pesan Terkirim ke WhatsApp

![Screenshot WA]([URL gambar kalian])
Deskripsi: [jelaskan singkat]


💻 Potongan Kode Penting

Pilih 2-3 bagian kode yang menurut kalian paling penting atau paling menantang, lalu jelaskan.

Kode 1: [Nama fitur — misal: State Keranjang di Root]

dart
[tempel kode kalian di sini]

Penjelasan: [jelaskan kenapa kode ini penting, apa fungsinya, dan kenapa kalian memilih cara ini]

Kode 2: [Nama fitur — misal: Generate Pesan WhatsApp]

dart
[tempel kode kalian di sini]

Penjelasan: [jelaskan]

Kode 3 (opsional): [Nama fitur]

dart
[tempel kode kalian di sini]

Penjelasan: [jelaskan]


🚧 Kendala yang Dihadapi & Cara Mengatasinya

Bagian ini paling penting — tunjukkan proses berpikir kalian, bukan cuma hasil akhir.

Kendala 1: [Judul kendala — misal: Badge keranjang tidak update]

Masalah: [jelaskan apa yang terjadi]

Pesan error (jika ada):

text
[tempel pesan error]

Cara saya mengatasinya: [jelaskan langkah-langkah — apakah tanya AI, baca dokumentasi, coba-coba sendiri?]

Pelajaran yang saya ambil: [1-2 kalimat]


Kendala 2: [Judul kendala]

Masalah: [jelaskan]

Cara saya mengatasinya: [jelaskan]

Pelajaran yang saya ambil: [jelaskan]


Kendala 3 (opsional): [Judul kendala]

Masalah: [jelaskan]

Cara saya mengatasinya: [jelaskan]

Pelajaran yang saya ambil: [jelaskan]


🤖 Peran AI dalam Proses Belajar Saya

Refleksikan bagaimana kalian menggunakan AI selama praktikum ini.

Prompt yang paling membantu saya:

"[tulis 1 prompt yang menurut kalian paling berguna]"

Prompt yang ternyata tidak membantu:

"[tulis 1 prompt yang hasilnya kurang memuaskan, dan kenapa]"

Yang saya sadari tentang belajar dengan AI:
[tulis 2-3 kalimat — misal: AI membantu ketika saya stuck, tapi saya tetap harus paham logikanya]


🎓 Refleksi Belajar

Jawab pertanyaan berikut dengan bahasa kalian sendiri:

1. Kenapa state keranjang ditaruh di MyApp, bukan di CartScreen?
[Jawaban kalian]

2. Apa fungsi Uri.encodeComponent() pada URL WhatsApp?
[Jawaban kalian]

3. Bagaimana cara menambah warung ke-4 di aplikasi?
[Jawaban kalian]

4. Konsep Flutter apa yang paling saya kuasai setelah praktikum ini?
[Jawaban kalian]

5. Konsep apa yang masih ingin saya pelajari lebih dalam?
[Jawaban kalian]


🚀 Tantangan yang Saya Coba (Opsional)

Centang tantangan yang berhasil kalian selesaikan.

  • □

    Level 1: Tombol hapus item di keranjang

  • □

    Level 2: Format harga jadi Rp 8.000 pakai intl

  • □

    Level 3: Fitur pencarian menu

  • □

    Level 4 (bonus): Simpan riwayat pesanan

Screenshot tantangan (jika ada):
![Screenshot Tantangan]([URL gambar])

Penjelasan singkat: [jelaskan tantangan yang dikerjakan dan bagaimana hasilnya]


🏁 Kesimpulan

[tulis 3-5 kalimat — apa yang kalian dapat dari praktikum ini, apa rencana pengembangan selanjutnya, dan bagaimana perasaan kalian setelah berhasil menyelesaikan aplikasi]


🔗 Link Terkait

  • Repo GitHub: [URL]

  • Video demo (opsional): [URL YouTube/Drive]

  • Postingan guru (acuan): [URL postingan guru kalian]


Postingan ini dibuat sebagai bukti praktikum Flutter — [Nama Kalian], [Kelas], [Tanggal]


📋 TEMPLATE — Selesai Copy di Sini

Share:

Tidak ada komentar:

Posting Komentar

Categories

Blogger Tricks

Blogger Themes