Praktikum III membahas Input Widgets & Forms pada Flutter, yaitu cara menangkap data dari pengguna melalui form, menggunakan controller, dropdown, serta validasi input sebelum data diproses.
TextFormField digunakan untuk menerima data dari pengguna sekaligus mendukung proses validasi melalui property validator. Validator memeriksa isi kolom dan mengembalikan null apabila data valid atau String berisi pesan error apabila data tidak valid.
GlobalKey<FormState> digunakan untuk mengontrol Form dan menjalankan proses validasi menggunakan method validate(). Dengan demikian, seluruh input dapat diperiksa ketika pengguna menekan tombol Simpan.
TextEditingController digunakan untuk mengambil teks yang dimasukkan pengguna pada field judul dan nominal. Controller perlu dibersihkan menggunakan method dispose() ketika halaman tidak lagi digunakan.
DropdownButtonFormField digunakan untuk memberikan pilihan kategori yang telah ditentukan. Pada praktikum ini kategori yang digunakan adalah Makanan, Transportasi, Hiburan, dan Lainnya.
lib/add_transaction_screen.dart
class AddTransactionScreen extends StatefulWidget {
const AddTransactionScreen({super.key});
@override
State<AddTransactionScreen> createState() =>
_AddTransactionScreenState();
}
Penggunaan StatefulWidget memungkinkan aplikasi memperbarui nilai state dropdown dan data input selama form digunakan.
final _formKey = GlobalKey<FormState>(); final _titleController = TextEditingController(); final _amountController = TextEditingController();
GlobalKey digunakan untuk memicu validasi, sedangkan controller digunakan untuk mengambil teks yang dimasukkan pengguna.
String _selectedCategory = 'Makanan'; final List<String> _categories = [ 'Makanan', 'Transportasi', 'Hiburan', 'Lainnya' ];
Nilai awal kategori adalah Makanan. Daftar kategori kemudian digunakan sebagai item pada DropdownButtonFormField.
@override
void dispose() {
_titleController.dispose();
_amountController.dispose();
super.dispose();
}
Method dispose memastikan controller yang digunakan oleh form dilepas ketika widget sudah tidak digunakan.
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
...
],
),
),
)
SingleChildScrollView digunakan agar tampilan tetap dapat digulir ketika keyboard muncul pada perangkat.
TextFormField(
controller: _titleController,
decoration: const InputDecoration(
labelText: "Judul Transaksi",
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.title),
),
validator: (value) {
if (value == null || value.isEmpty) {
return "Judul transaksi tidak boleh kosong";
}
return null;
},
)
Jika pengguna tidak mengisi judul, validator akan menampilkan pesan error. Jika input valid, validator mengembalikan nilai null.
TextFormField(
controller: _amountController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
labelText: "Nominal (Rp)",
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.attach_money),
),
validator: (value) {
if (value == null || value.isEmpty) {
return "Nominal wajib diisi";
}
if (int.tryParse(value) == null) {
return "Harus berupa angka bulat yang valid";
}
return null;
},
)
Validator nominal memeriksa dua kondisi, yaitu nominal tidak boleh kosong dan nilai yang dimasukkan harus berupa angka bulat yang valid.
DropdownButtonFormField<String>(
value: _selectedCategory,
decoration: const InputDecoration(
labelText: "Kategori",
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.category),
),
items: _categories.map((String category) {
return DropdownMenuItem<String>(
value: category,
child: Text(category),
);
}).toList(),
onChanged: (String? newValue) {
setState(() {
_selectedCategory = newValue!;
});
},
)
Ketika pengguna memilih kategori baru, nilai _selectedCategory diperbarui menggunakan setState().
onPressed: () {
if (_formKey.currentState!.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Tersimpan: ${_titleController.text} '
'(Rp ${_amountController.text})',
),
backgroundColor: Colors.green,
),
);
}
}
Jika seluruh input memenuhi aturan validasi, aplikasi menampilkan SnackBar berisi informasi transaksi yang berhasil disimpan.
lib/main.dart
import 'package:flutter/material.dart';
import 'add_transaction_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Expense Tracker - Modul 3',
theme: ThemeData(
primarySwatch: Colors.deepPurple,
),
home: const AddTransactionScreen(),
);
}
}
Setelah main.dart dijalankan, aplikasi langsung membuka halaman formulir transaksi.
Pada tugas praktikum Modul 3, ditambahkan satu input baru sebelum tombol Simpan untuk mengisi Tanggal Transaksi. Modul menyediakan dua alternatif, yaitu menggunakan TextFormField biasa dengan format teks DD/MM/YYYY atau menggunakan tombol tambahan yang memanggil fungsi showDatePicker. Input tanggal wajib diberikan validasi agar tidak boleh kosong. fileciteturn0file0L195-L201
final _dateController = TextEditingController();
TextFormField(
controller: _dateController,
decoration: const InputDecoration(
labelText: "Tanggal Transaksi",
hintText: "DD/MM/YYYY",
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.calendar_today),
),
validator: (value) {
if (value == null || value.isEmpty) {
return "Tanggal transaksi wajib diisi";
}
return null;
},
)
Field tanggal wajib diisi. Jika field kosong ketika tombol Simpan ditekan, validator akan menampilkan pesan error.
Future<void> _pickDate() async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
);
if (picked != null) {
setState(() {
_dateController.text =
"${picked.day.toString().padLeft(2, '0')}/"
"${picked.month.toString().padLeft(2, '0')}/"
"${picked.year}";
});
}
}
Ketika pengguna memilih tanggal pada kalender, tanggal tersebut dimasukkan ke dalam field dengan format DD/MM/YYYY.
onPressed: () {
if (_formKey.currentState!.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Data transaksi berhasil divalidasi"),
),
);
}
}
Dengan demikian, seluruh field utama pada form, termasuk tanggal transaksi, diperiksa sebelum data diproses.
Berdasarkan praktikum yang telah dilakukan, aplikasi berhasil menerapkan input widget menggunakan TextFormField untuk menerima judul transaksi dan nominal. Setiap input dihubungkan dengan TextEditingController sehingga nilai yang dimasukkan pengguna dapat diambil ketika proses penyimpanan dilakukan.
Penggunaan keyboardType: TextInputType.number pada nominal membuat field tersebut digunakan khusus untuk input angka. Validator juga memastikan nominal tidak kosong dan harus berupa angka bulat yang valid.
Dropdown berhasil digunakan untuk memilih kategori transaksi. Nilai kategori disimpan pada state _selectedCategory dan diperbarui menggunakan setState() ketika pengguna memilih pilihan yang berbeda.
Proses validasi dilakukan menggunakan Form dan GlobalKey<FormState>. Ketika tombol Simpan ditekan, method validate() memeriksa seluruh field. Jika input memenuhi ketentuan, aplikasi menampilkan SnackBar sebagai informasi bahwa data telah berhasil diproses.
Pada tugas praktikum, input tanggal transaksi ditambahkan sebelum tombol Simpan. Tanggal dapat dimasukkan menggunakan TextFormField dengan format DD/MM/YYYY atau dipilih melalui fungsi showDatePicker. Field tanggal juga diberikan validasi wajib diisi sesuai ketentuan tugas pada modul.
Dari praktikum yang telah dilakukan dapat disimpulkan bahwa Flutter menyediakan berbagai input widget yang dapat digunakan untuk menangkap data dari pengguna melalui sebuah form. TextFormField dapat digunakan untuk menerima input sekaligus melakukan validasi menggunakan property validator.
TextEditingController digunakan untuk mengambil nilai input, sedangkan DropdownButtonFormField digunakan untuk memberikan pilihan kategori kepada pengguna. Penggunaan Form dan GlobalKey<FormState> memungkinkan seluruh input divalidasi sebelum data diproses.
Pada tugas praktikum, form dikembangkan dengan menambahkan input tanggal transaksi yang wajib diisi. Dengan penerapan validasi, aplikasi dapat mencegah data kosong atau input yang tidak sesuai sebelum diproses.