Skip to main content
Praktikum 3·
21 September 2026

Input Widgets & Forms

Menangkap Data, Dropdown, dan Validasi

ITujuan Praktikum

Setelah menyelesaikan praktikum ini, mahasiswa diharapkan mampu:

  • ✓Menangkap teks dari pengguna melalui TextEditingController.
  • ✓Menerapkan widget dropdown statis (DropdownButtonFormField) untuk pemilihan kategori.
  • ✓Menerapkan validasi input untuk mencegah data kosong atau format yang salah.
  • ✓Menambahkan input tanggal transaksi menggunakan showDatePicker beserta validasinya (bagian tugas).

IIDasar Teori

Form & GlobalKey

Widget Form berfungsi sebagai wadah utama yang mengelompokkan beberapa input agar dapat divalidasi secara bersamaan. GlobalKey<FormState> digunakan sebagai kunci untuk mengakses dan memicu proses validasi seluruh field yang berada di dalam Form melalui pemanggilan _formKey.currentState!.validate().

TextFormField & TextEditingController

TextFormField merupakan kolom input teks yang sudah terintegrasi dengan mekanisme validasi bawaan Form. TextEditingController digunakan sebagai pengendali (controller) untuk membaca dan mengelola teks yang diketikkan pengguna pada masing-masing field, seperti judul transaksi dan nominal.

Validasi Input

Validasi digunakan untuk mencegah data yang tidak sesuai format masuk ke dalam sistem sebelum disimpan. Mekanismenya menggunakan properti validator pada TextFormField, berupa fungsi yang memeriksa isi kolom:

  • Jika data valid, validator mengembalikan null (tidak ada error).

  • Jika data tidak valid, validator mengembalikan sebuah String berisi pesan error yang akan ditampilkan berwarna merah di bawah field.

DropdownButtonFormField

Widget ini digunakan untuk menyediakan pilihan kategori yang bersifat statis kepada pengguna, sehingga input kategori tidak berupa teks bebas melainkan dipilih dari daftar yang telah ditentukan (Makanan, Transportasi, Hiburan, Lainnya).

showDatePicker

showDatePicker adalah fungsi bawaan Flutter yang menampilkan dialog kalender interaktif kepada pengguna untuk memilih tanggal. Fungsi ini bersifat asynchronous dan mengembalikan objek DateTime yang dipilih pengguna, atau null jika dialog dibatalkan.

IIILangkah Kerja Praktikum

1

Persiapan State & Controller

Halaman dibuat sebagai StatefulWidget karena isi formulir (teks dan dropdown) akan terus berubah saat pengguna berinteraksi. Disiapkan sebuah GlobalKey untuk memicu validasi, serta TextEditingController untuk judul dan nominal.

dart
class _AddTransactionScreenState extends State<AddTransactionScreen> {
  // Kunci utama untuk mengontrol Form
  final _formKey = GlobalKey<FormState>();
 
  // Controller pembaca ketikan
  final _titleController = TextEditingController();
  final _amountController = TextEditingController();
 
  // Default opsi dropdown
  String _selectedCategory = 'Makanan';
  final List<String> _categories = [
    'Makanan',
    'Transportasi',
    'Hiburan',
    'Lainnya',
  ];
 
  @override
  void dispose() {
    // Bersihkan controller saat halaman ditutup agar tidak memory leak
    _titleController.dispose();
    _amountController.dispose();
    super.dispose();
  }
}
2

Membuat Kolom Input (TextFormField)

Seluruh kolom dibungkus dengan widget Form dan dihubungkan dengan _formKey. Setiap TextFormField dihubungkan dengan controller masing-masing, dan khusus kolom nominal digunakan keyboardType: TextInputType.number.

php
Form(
  key: _formKey,
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.stretch,
    children: [
      // Input 1: Judul Transaksi
      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;
        },
      ),
      const SizedBox(height: 16),
 
      // Input 2: Nominal Saldo (Keyboard Angka)
      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;
        },
      ),
    ],
  ),
)
3

Dropdown Kategori & Tombol Simpan

DropdownButtonFormField digunakan untuk pilihan kategori transaksi, dan ElevatedButton digunakan untuk memicu validasi seluruh form melalui _formKey.currentState!.validate().

dart
// Input 3: Kategori Dropdown
DropdownButtonFormField<String>(
  initialValue: _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!;
    });
  },
),
const SizedBox(height: 32),
 
// Tombol Simpan
ElevatedButton(
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.deepPurple,
    foregroundColor: Colors.white,
    padding: const EdgeInsets.symmetric(vertical: 16),
  ),
  onPressed: () {
    if (_formKey.currentState!.validate()) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text(
            'Tersimpan: ${_titleController.text} '
            '(Rp ${_amountController.text})',
          ),
          backgroundColor: Colors.green,
        ),
      );
    }
  },
  child: const Text(
    "Simpan Transaksi",
    style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
  ),
)
4

Konfigurasi main.dart

File main.dart menjadi entry point aplikasi dan langsung mengarahkan halaman utama ke AddTransactionScreen.

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(),
    );
  }
}
5

Menjalankan Aplikasi

Aplikasi dijalankan pada emulator/device untuk menguji tampilan formulir dan mekanisme validasi.

IVLatihan dan Tugas

1

Menambahkan Controller Tanggal

Sebuah TextEditingController baru (_dateController) ditambahkan pada state untuk menampung dan menampilkan tanggal yang dipilih pengguna, serta di-dispose bersama controller lainnya.

dart
final _dateController = TextEditingController();
 
@override
void dispose() {
  _titleController.dispose();
  _amountController.dispose();
  _dateController.dispose(); // dispose controller tanggal
  super.dispose();
}
2

Widget Input Tanggal & Date Picker

Field tanggal dibuat readOnly agar pengguna tidak mengetik manual, melainkan wajib memilih tanggal melalui dialog kalender. Saat field ditekan (onTap), fungsi showDatePicker dipanggil secara asynchronous; hasil tanggal yang dipilih kemudian diformat menjadi DD/MM/YYYY dan ditampilkan pada _dateController.

dart
// Input 4: Tanggal Transaksi
TextFormField(
  controller: _dateController,
  readOnly: true,
  decoration: const InputDecoration(
    labelText: "Tanggal Transaksi",
    hintText: "DD/MM/YYYY",
    border: OutlineInputBorder(),
    prefixIcon: Icon(Icons.calendar_today),
  ),
  onTap: () async {
    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}";
      });
    }
  },
  validator: (value) {
    if (value == null || value.isEmpty) {
      return "Tanggal transaksi wajib diisi";
    }
    return null;
  },
),
3

Integrasi Tanggal ke Pesan SnackBar

Setelah validasi seluruh form berhasil (termasuk tanggal), pesan konfirmasi pada SnackBar diperbarui agar turut menampilkan tanggal transaksi yang dipilih.

dart
onPressed: () {
  if (_formKey.currentState!.validate()) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(
          'Tersimpan: ${_titleController.text} - '
          '${_dateController.text} (Rp ${_amountController.text})',
        ),
        backgroundColor: Colors.green,
      ),
    );
  }
},
4

Hasil Pengujian Tugas

VKesimpulan

Berdasarkan praktikum yang telah dilakukan, dapat disimpulkan beberapa hal berikut:

  • Widget Form beserta GlobalKey<FormState> memudahkan pengelolaan dan validasi banyak input sekaligus hanya dengan satu kali pemanggilan validate().

  • TextEditingController berperan penting dalam membaca nilai input pengguna secara real-time, baik untuk kolom teks biasa maupun kolom yang bersifat readOnly seperti tanggal.

  • Properti validator pada TextFormField efektif mencegah data kosong maupun data dengan format yang tidak sesuai (misalnya nominal yang bukan angka) agar tidak tersimpan ke sistem.

  • DropdownButtonFormField cocok digunakan untuk input dengan pilihan yang bersifat tetap/statis seperti kategori transaksi.

  • Fungsi showDatePicker mempermudah pengguna memilih tanggal secara akurat melalui antarmuka kalender, dibandingkan mengetik tanggal secara manual, sekaligus mengurangi risiko kesalahan format input.

  • Kombinasi StatefulWidget, controller, dan validator menghasilkan formulir input transaksi yang lengkap, interaktif, dan tervalidasi dengan baik.