Praktikum Mobile Application 3

Form Input Transaksi pada Flutter

Implementasi form pencatatan transaksi menggunakan TextFormField, DropdownButtonFormField, validasi input, showDatePicker, dan SnackBar pada Flutter.

Praktikum 3 Flutter
Form
Input transaksi
Validasi
Periksa field wajib
Date Picker
Pilih tanggal transaksi
SnackBar
Feedback penyimpanan

1. Tujuan Praktikum

Memahami pembuatan form transaksi di Flutter menggunakan StatefulWidget, Form, TextFormField, DropdownButtonFormField, controller, validasi, dan SnackBar. Pada tugas, form dikembangkan dengan input tanggal melalui showDatePicker.

Form Input

Mengelola judul transaksi, nominal, kategori, dan tanggal.

Validasi

Memeriksa input sebelum transaksi dikirim.

Date Picker

Memilih tanggal dan menampilkannya dalam format DD/MM/YYYY.

Teknologi

Flutter dengan StatefulWidget dan Material components.

2. Konsep yang Digunakan

StatefulWidget & setState()

Menjaga dan memperbarui kategori serta tanggal yang dipilih selama halaman digunakan.

Form & GlobalKey

Mengelompokkan input dan menjalankan validator melalui GlobalKey<FormState>.

Controller & Input

TextEditingController membaca teks; TextFormField dan DropdownButtonFormField menyediakan input.

showDatePicker

Membuka kalender dan mengembalikan tanggal yang dipilih.

SnackBar

Menampilkan feedback transaksi setelah seluruh input valid.

dispose()

Melepas controller ketika State tidak lagi digunakan.

3. Persiapan Project

Project menggunakan dua file utama di dalam folder lib.

lib/
├── main.dart
└── add_transaction_screen.dart

4. Langkah-Langkah Praktikum

01. Siapkan project

Buat aplikasi Flutter dan siapkan main.dart serta add_transaction_screen.dart.

02. Buat halaman form

Buat AddTransactionScreen sebagai StatefulWidget dan susun input di dalam Form.

03. Validasi dan simpan

Validasi field, pilih kategori dan tanggal, lalu tampilkan feedback SnackBar.

5. Implementasi Form

Form menggunakan GlobalKey<FormState> untuk mengoordinasikan input, sedangkan controller menyimpan teks judul, nominal, dan tanggal. SingleChildScrollView menjaga seluruh form tetap dapat diakses pada layar pendek.

Input transaksi

TextFormField menerima judul dan nominal. Nominal memakai keyboard angka dan diperiksa agar merupakan bilangan bulat yang valid.

Siklus widget

StatefulWidget dan setState() memperbarui pilihan kategori/tanggal. Controller dibersihkan melalui dispose().

6. Validasi Input

Judul

Wajib diisi sebelum transaksi dapat disimpan.

Nominal

Wajib diisi dan harus berupa angka bulat yang valid.

Tanggal

Tanggal transaksi wajib diisi sebelum form dinyatakan valid.

8. Input Tanggal Transaksi

Field Tanggal Transaksi diletakkan sebelum tombol Simpan Transaksi. Field bersifat read-only agar tanggal dipilih melalui kalender, dan wajib diisi.

9. Implementasi showDatePicker

showDatePicker menampilkan kalender dengan rentang tahun 2000 hingga 2100. Tanggal terpilih diformat menjadi DD/MM/YYYY, misalnya 15/09/2026.

Validasi tanggal

Jika tanggal belum dipilih, validator menampilkan pesan “Tanggal transaksi wajib diisi” dan pengiriman form dihentikan.

10. Source Code

Source code final Praktikum 3, termasuk tugas input tanggal.

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

add_transaction_screen.dart

import 'package:flutter/material.dart';

class AddTransactionScreen extends StatefulWidget {
  const AddTransactionScreen({super.key});

  @override
  State<AddTransactionScreen> createState() => _AddTransactionScreenState();
}

class _AddTransactionScreenState extends State<AddTransactionScreen> {
  final _formKey = GlobalKey<FormState>();
  final _titleController = TextEditingController();
  final _amountController = TextEditingController();
  final _dateController = TextEditingController();

  String _selectedCategory = 'Makanan';
  final List<String> _categories = [
    'Makanan',
    'Transportasi',
    'Hiburan',
    'Lainnya',
  ];

  Future<void> _selectDate() async {
    final DateTime? pickedDate = await showDatePicker(
      context: context,
      initialDate: DateTime.now(),
      firstDate: DateTime(2000),
      lastDate: DateTime(2100),
    );

    if (pickedDate != null) {
      final String day = pickedDate.day.toString().padLeft(2, '0');
      final String month = pickedDate.month.toString().padLeft(2, '0');
      final String year = pickedDate.year.toString();

      setState(() {
        _dateController.text = '$day/$month/$year';
      });
    }
  }

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

  @override
  void dispose() {
    _titleController.dispose();
    _amountController.dispose();
    _dateController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Catat Transaksi Baru'),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              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),
              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;
                },
              ),
              const SizedBox(height: 16),
              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) {
                  if (newValue == null) return;
                  setState(() {
                    _selectedCategory = newValue;
                  });
                },
              ),
              const SizedBox(height: 16),
              TextFormField(
                controller: _dateController,
                readOnly: true,
                decoration: InputDecoration(
                  labelText: 'Tanggal Transaksi',
                  hintText: 'DD/MM/YYYY',
                  border: const OutlineInputBorder(),
                  prefixIcon: const Icon(Icons.calendar_today),
                  suffixIcon: IconButton(
                    icon: const Icon(Icons.add),
                    onPressed: _selectDate,
                  ),
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Tanggal transaksi wajib diisi';
                  }
                  return null;
                },
              ),
              const SizedBox(height: 24),
              SizedBox(
                width: double.infinity,
                child: ElevatedButton(
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.deepPurple,
                    foregroundColor: Colors.white,
                    padding: const EdgeInsets.symmetric(vertical: 16),
                  ),
                  onPressed: _saveTransaction,
                  child: const Text('Simpan Transaksi'),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

11. Pengujian

Validasi judul dan nominal

Kosongkan judul atau nominal, lalu kirim form.

Hasil: pesan validasi muncul dan data tidak dikirim.

Validasi nominal angka

Masukkan nominal yang bukan angka bulat.

Hasil: input ditolak oleh validator.

Validasi tanggal

Kirim form tanpa memilih tanggal.

Hasil: “Tanggal transaksi wajib diisi”.

Simpan transaksi

Isi SPAGGETI, 10, Makanan, dan 15/09/2026.

Hasil: SnackBar menampilkan transaksi tersimpan.

12. Hasil Praktikum 3

Implementasi awal menggunakan tiga input: judul transaksi, nominal, dan kategori. Contoh yang diuji adalah CROISSANT, Rp 5, kategori Makanan. SnackBar berhasil menampilkan “Tersimpan: CROISSANT (Rp 5)”.

13. Hasil Tugas Praktikum 3

Tugas menambahkan Tanggal Transaksi sebagai input keempat dan mewajibkannya sebelum submit. Data SPAGGETI, Rp 10, Makanan, dan 15/09/2026 menghasilkan SnackBar “Tersimpan: SPAGGETI (Rp 10) - 15/09/2026”.

14. Kesimpulan

Praktikum berhasil membangun form pencatatan transaksi Flutter menggunakan StatefulWidget, Form, TextFormField, DropdownButtonFormField, GlobalKey<FormState>, TextEditingController, dan SnackBar.

Pada tugas, input tanggal ditambahkan menggunakan showDatePicker dan diformat menjadi DD/MM/YYYY. Validasi memastikan tanggal wajib dipilih sebelum transaksi tersimpan.

Hasil Akhir

Form transaksi divalidasi dan memberikan feedback berhasil dengan tanggal yang dipilih.

15. Dokumentasi Hasil

Karimah Irsyadiyah

Mahasiswa Informatika, Universitas Andalas

Profil Profesional

Karimah Irsyadiyah adalah mahasiswa Informatika di Universitas Andalas yang menekuni pengembangan aplikasi mobile menggunakan Flutter serta dokumentasi hasil praktikum secara rapi dan profesional.