Membuat Aplikasi Flutter yang Bisa Berinteraksi dengan Pengguna
Gunakan tombol ◀ Sebelumnya / Berikutnya ▶ atau panah keyboard ← →
Inilah perjalanan kita. Setiap "pemberhentian" adalah satu widget input yang akan membuat aplikasimu hidup.
Di BAB 6 kamu sudah belajar menyusun tampilan dengan:
Sekarang tampilan itu akan kita isi dengan input yang bisa diketik & dipilih user. Tata letak (BAB 6) + interaksi (BAB 7) = aplikasi yang nyata.
Tanpa form, aplikasi hanya bisa menampilkan. Dengan form, aplikasi bisa mendengar penggunanya — login, daftar, mengisi survei, mencari produk. Inilah jembatan antara manusia dan aplikasi.
TextField adalah widget untuk menerima input teks dari pengguna — seperti kolom nama, email, atau kata sandi.
Tamu menulis namanya di buku tamu…
Bayangkan sebuah buku tamu di pintu masuk acara. Setiap tamu menuliskan namanya di baris kosong. TextField adalah baris kosong itu — tempat user "menulis" data ke dalam aplikasimu.
main.dart — TextField dasarTextField( decoration: InputDecoration( labelText: 'Nama Lengkap', hintText: 'Tulis namamu di sini', border: OutlineInputBorder(), ), onChanged: (value) { print('User mengetik: $value'); }, )
TextField bisa "dirias" dan "dikendalikan": hint, controller, dan style.
Controller seperti remote TV: kamu bisa membaca isi field dan mengubahnya dari kode kapan saja.
controller + stylefinal nameCtrl = TextEditingController(); TextField( controller: nameCtrl, obscureText: false, // true = mode password style: TextStyle(color: Colors.white), decoration: InputDecoration( hintText: 'Masukkan email', prefixIcon: Icon(Icons.email), filled: true, ), ) // Membaca isi field kapan saja: print(nameCtrl.text);
TextFormField = TextField + kemampuan validasi. Ia dipakai di dalam sebuah Form.
Guru memeriksa: "Kolom ini tidak boleh kosong!"
TextField biasa = kertas coretan bebas. TextFormField = formulir resmi yang akan diperiksa guru. Jika ada kolom yang salah/kosong, formulir ditolak sampai diperbaiki.
TextFormField + validatorTextFormField( decoration: InputDecoration( labelText: 'Email', ), validator: (value) { if (value == null || value.isEmpty) { return 'Email tidak boleh kosong'; } return null; // null artinya VALID }, )
Hanya menerima teks. Tidak tahu apakah benar/salah.
Punya validator & hidup di dalam Form.
Validator adalah satpam input: ia memeriksa data sebelum diterima. Return null = lolos, return teks = pesan error.
validator lengkapvalidator: (value) { if (value == null || value.isEmpty) { return 'Wajib diisi'; } if (!value.contains('@')) { return 'Email harus mengandung @'; } if (value.length < 5) { return 'Terlalu pendek'; } return null; // semua aturan lolos }
return null = data VALID (tidak ada masalah).
return 'teks' = data SALAH, teks tampil sebagai pesan error merah.
Flowchart di kiri "menggambar dirinya sendiri" setiap kali slide ini dibuka.
DropdownButton membiarkan user memilih satu opsi dari daftar yang muncul ke bawah.
"Pilih satu menu, ya!"
Seperti buku menu restoran: daftarnya tertutup, lalu terbuka saat kamu lihat, dan kamu hanya boleh memilih satu makanan. Itulah cara Dropdown bekerja.
DropdownButtonString? pilihan; final list = ['SMA', 'SMK', 'MA']; DropdownButton<String>( value: pilihan, hint: Text('Pilih jenjang'), items: list.map((j) { return DropdownMenuItem( value: j, child: Text(j), ); }).toList(), onChanged: (val) { setState(() => pilihan = val); }, )
Checkbox adalah pilihan ya / tidak (true / false). Bisa banyak yang dicentang sekaligus.
Centang yang sudah dibeli.
Seperti daftar belanja: kamu boleh mencentang beberapa item sekaligus. Setiap kotak punya status sendiri — tercentang (true) atau tidak (false).
CheckboxListTilebool setuju = false; CheckboxListTile( title: Text('Saya setuju syarat & ketentuan'), value: setuju, onChanged: (val) { setState(() => setuju = val!); }, )
Radio memaksa user memilih hanya satu dari beberapa opsi — sisanya otomatis batal.
Tekan satu tombol → tombol lain otomatis lepas.
Nama "Radio" berasal dari tombol stasiun radio mobil jadul: saat kamu menekan satu tombol, tombol lain otomatis terlepas. Hanya satu yang aktif.
Radio<String>String? gender; Radio<String>( value: 'L', groupValue: gender, onChanged: (val) => setState(() => gender = val), ), Radio<String>( value: 'P', groupValue: gender, onChanged: (val) => setState(() => gender = val), ),
showDatePicker memunculkan kalender agar user memilih tanggal — misalnya tanggal lahir.
Tunjuk satu tanggal di kalender.
Seperti menunjuk satu kotak di kalender dinding. Daripada mengetik "12/08/2009" yang rawan salah, user cukup menunjuk tanggalnya.
showDatePicker (async / await)DateTime? tanggal; Future<void> pilihTanggal() async { final hasil = await showDatePicker( context: context, initialDate: DateTime.now(), firstDate: DateTime(2000), lastDate: DateTime(2030), ); if (hasil != null) { setState(() => tanggal = hasil); } }
showTimePicker memunculkan jam agar user memilih waktu — misalnya jam janji temu.
Atur jam & menit lalu OK.
Persis seperti menyetel alarm di HP: kamu memutar jam & menit lalu menekan OK. Time Picker mengembalikan objek TimeOfDay.
showTimePickerTimeOfDay? waktu; Future<void> pilihWaktu() async { final hasil = await showTimePicker( context: context, initialTime: TimeOfDay.now(), ); if (hasil != null) { setState(() => waktu = hasil); } } // Menampilkan: waktu!.format(context)
Saat tombol Submit ditekan, Flutter menjalankan SEMUA validator sekaligus. Inilah alurnya.
Satu GlobalKey<FormState> memegang seluruh form. Memanggil .validate() akan menjalankan validator setiap field. Jika ada SATU saja yang gagal, hasilnya false dan pesan error muncul serentak.
Tiga bahan utama: GlobalKey, widget Form, dan pemanggilan .validate().
struktur Form lengkapfinal _formKey = GlobalKey<FormState>(); Form( key: _formKey, child: Column( children: [ TextFormField( validator: (v) => v!.isEmpty ? 'Wajib diisi' : null, ), ElevatedButton( onPressed: () { if (_formKey.currentState!.validate()) { // semua field valid → lanjut ScaffoldMessenger.of(context) .showSnackBar(SnackBar( content: Text('Berhasil!'), )); } }, child: Text('Submit'), ), ], ), )
"KTP" unik untuk form, agar kode bisa mengakses & mengontrolnya.
Pembungkus semua field. Memberi mereka kemampuan validasi bersama.
Tombol "periksa semua". Mengembalikan true bila seluruh field lolos.
Menyimpan nilai tiap field lewat onSaved.
Sekarang kita rangkai TextFormField, Dropdown, Checkbox, dan Radio menjadi satu form hidup.
form gabunganForm( key: _formKey, child: Column(children: [ TextFormField( decoration: InputDecoration(labelText: 'Nama'), validator: (v) => v!.isEmpty ? 'Isi nama' : null, ), DropdownButtonFormField<String>( decoration: InputDecoration(labelText: 'Jenjang'), items: ['SMA','SMK','MA'].map((e) => DropdownMenuItem(value: e, child: Text(e)) ).toList(), validator: (v) => v == null ? 'Pilih jenjang' : null, onChanged: (v) {}, ), CheckboxListTile( title: Text('Setuju S&K'), value: setuju, onChanged: (v) => setState(() => setuju = v!), ), ]), )
Coba tekan Submit dengan field kosong → muncul error. Isi semua → SnackBar hijau.
Proyek puncak BAB 7: satu form pendaftaran lengkap yang memakai semua widget yang kita pelajari.
Kita bangun bertahap: Slide 17 = struktur form & field, Slide 18 = validasi + tombol submit.
Kita siapkan StatefulWidget, variabel state, dan kerangka Form-nya.
form_pendaftaran.dart — bagian 1/2class FormPendaftaran extends StatefulWidget { @override State<FormPendaftaran> createState() => _FormPendaftaranState(); } class _FormPendaftaranState extends State<FormPendaftaran> { final _formKey = GlobalKey<FormState>(); final namaCtrl = TextEditingController(); final emailCtrl = TextEditingController(); String? jenjang; // untuk Dropdown String? gender; // untuk Radio DateTime? tglLahir; // untuk Date Picker bool setuju = false; // untuk Checkbox @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Pendaftaran Siswa')), body: Form( key: _formKey, child: ListView( padding: EdgeInsets.all(16), children: [ TextFormField( controller: namaCtrl, decoration: InputDecoration(labelText: 'Nama Lengkap'), validator: (v) => v!.isEmpty ? 'Nama wajib diisi' : null, ), TextFormField( controller: emailCtrl, decoration: InputDecoration(labelText: 'Email'), validator: (v) => !v!.contains('@') ? 'Email tidak valid' : null, ), DropdownButtonFormField<String>( decoration: InputDecoration(labelText: 'Jenjang'), value: jenjang, items: ['SMA','SMK','MA'].map((e) => DropdownMenuItem(value: e, child: Text(e))).toList(), validator: (v) => v == null ? 'Pilih jenjang' : null, onChanged: (v) => setState(() => jenjang = v), ), // ... lanjut ke Bagian 2 ], ), ), ); } }
Radio, Checkbox, dan tombol Daftar yang menjalankan validasi seluruh form.
form_pendaftaran.dart — bagian 2/2// (lanjutan children ListView) // --- Radio Jenis Kelamin --- Row(children: [ Radio<String>(value: 'L', groupValue: gender, onChanged: (v) => setState(() => gender = v)), Text('Laki-laki'), Radio<String>(value: 'P', groupValue: gender, onChanged: (v) => setState(() => gender = v)), Text('Perempuan'), ]), // --- Checkbox Persetujuan --- CheckboxListTile( title: Text('Saya setuju syarat & ketentuan'), value: setuju, onChanged: (v) => setState(() => setuju = v!), ), // --- Tombol Daftar --- ElevatedButton( onPressed: () { if (_formKey.currentState!.validate() && setuju) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text( 'Selamat datang, ${namaCtrl.text}!')), ); } else if (!setuju) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Centang persetujuan dulu')), ); } }, child: Text('Daftar Sekarang'), ),
Form pendaftaran lengkap dengan validasi penuh. Inilah pondasi semua aplikasi nyata: login, registrasi, checkout, dan survei.
BAB selanjutnya: State Management & Navigasi antar halaman — mengirim data form ke layar lain. Form yang kamu kuasai sekarang adalah fondasinya.
Buatlah sebuah halaman Login Flutter yang berisi:
Kumpulkan: screenshot tampilan + file .dart. Kerjakan sendiri-sendiri.
Bersama kelompok (3–4 orang), bangun Form Pemesanan Makanan dengan:
Presentasikan hasil di depan kelas. Setiap anggota menjelaskan bagiannya.
Tantangan akhir: buat Form Survei Kepuasan Siswa bertema bebas, dengan minimal:
Aplikasi berjalan + video demo singkat (1–2 menit) + kode sumber rapi.
Desain UI menarik, pesan error yang ramah, dan penggunaan TextEditingController yang benar.
🎓 Selamat! Kamu telah menyelesaikan BAB 7 — Form & Input User.