image

26 Feb 2023

9K

35K

Membuat Form Dinamis di Flutter dengan Reactive Forms

Mengembangkan aplikasi Flutter seringkali melibatkan pengelolaan formulir (form), dan tidak jarang kebutuhan akan form yang dinamis muncul. Form dinamis adalah form yang strukturnya dapat berubah saat runtime—misalnya, menambah atau menghapus bidang input berdasarkan interaksi pengguna, atau menampilkan bagian form yang berbeda tergantung pada pilihan tertentu. Mengelola state dan validasi form dinamis dengan pendekatan tradisional (misalnya, menggunakan TextEditingController secara manual) dapat menjadi rumit dan rentan kesalahan. Di sinilah Reactive Forms hadir sebagai solusi yang elegan dan powerful.

Apa itu Reactive Forms?

Reactive Forms adalah package populer di Flutter yang terinspirasi oleh implementasi form di Angular. Ia menyediakan pendekatan deklaratif dan reaktif untuk mengelola state form. Daripada memanipulasi nilai input secara langsung melalui controller, Reactive Forms memungkinkan Anda untuk mendefinisikan struktur form Anda sebagai model data (FormGroup, FormControl, FormArray) yang kemudian secara otomatis diikatkan ke widget UI Anda.

  • FormControl: Merepresentasikan bidang input tunggal (misalnya, nama, email).
  • FormGroup: Mengelompokkan beberapa FormControl menjadi satu unit logis (misalnya, form login yang berisi email dan password).
  • FormArray: Mengelola array dari FormControl atau FormGroup, yang sangat cocok untuk item berulang atau daftar dinamis.

Mengapa Reactive Forms untuk Form Dinamis?

Reactive Forms sangat unggul dalam skenario form dinamis karena beberapa alasan utama:

  • Manajemen State yang Terpusat: Seluruh state form diwakili oleh objek FormGroup atau FormArray, membuatnya mudah untuk menambah, menghapus, atau memodifikasi bidang secara terprogram.
  • Validasi yang Kuat: Validator dapat diterapkan secara dinamis ke FormControl atau FormGroup, dan status validasi (misalnya, isValid, hasErrors) diperbarui secara reaktif.
  • Abstraksi UI: Form model terpisah dari representasi UI, memungkinkan Anda untuk fokus pada logika bisnis tanpa terjebak dalam detail widget.
  • Kemudahan Pengujian: Karena form model adalah objek Dart murni, ia dapat diuji secara independen tanpa perlu rendering UI.

Memulai dengan Reactive Forms

Langkah pertama adalah menambahkan dependensi ke file pubspec.yaml Anda:

dependencies:
  flutter:
    sdk: flutter
  reactive_forms: ^<versi_terbaru> # Ganti dengan versi terbaru

Kemudian, jalankan flutter pub get.

Struktur dasar sebuah form melibatkan pembuatan FormGroup dan mengikatnya ke widget ReactiveForm:

final form = FormGroup({
  'namaDepan': FormControl(value: '', validators: [Validators.required]),
  'email': FormControl(value: '', validators: [Validators.required, Validators.email]),
});

// Dalam widget build:
ReactiveForm(
  formGroup: form,
  child: Column(
    children: [
      ReactiveTextField(
        formControlName: 'namaDepan',
        decoration: const InputDecoration(labelText: 'Nama Depan'),
      ),
      ReactiveTextField(
        formControlName: 'email',
        decoration: const InputDecoration(labelText: 'Email'),
      ),
      ElevatedButton(
        onPressed: () {
          if (form.valid) {
            print(form.value);
          } else {
            form.markAllAsTouched();
          }
        },
        child: const Text('Kirim'),
      ),
    ],
  ),
)

Mengimplementasikan Form Dinamis dengan FormArray

FormArray adalah kunci untuk membangun form dinamis, terutama ketika Anda memiliki daftar item yang berulang, seperti daftar anggota keluarga, hobi, atau detail produk.

Skenario: Daftar Item Belanja

Misalkan kita ingin membuat form di mana pengguna dapat menambahkan beberapa item belanja, dan setiap item memiliki nama dan kuantitasnya sendiri.

import 'package:flutter/material.dart';
import 'package:reactive_forms/reactive_forms.dart';

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

  @override
  State createState() => _ShoppingFormState();
}

class _ShoppingFormState extends State {
  final form = FormGroup({
    'namaPelanggan': FormControl(value: '', validators: [Validators.required]),
    'items': FormArray([
      _createShoppingItem(), // Item awal
    ]),
  });

  static FormGroup _createShoppingItem() {
    return FormGroup({
      'namaProduk': FormControl(value: '', validators: [Validators.required]),
      'kuantitas': FormControl(value: 1, validators: [Validators.required, Validators.min(1)]),
    });
  }

  FormArray get items => form.control('items') as FormArray;

  void _addShoppingItem() {
    items.add(_createShoppingItem());
  }

  void _removeShoppingItem(int index) {
    items.removeAt(index);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Form Belanja Dinamis')),
      body: ReactiveForm(
        formGroup: form,
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              ReactiveTextField(
                formControlName: 'namaPelanggan',
                decoration: const InputDecoration(labelText: 'Nama Pelanggan'),
              ),
              const SizedBox(height: 20),
              Text('Daftar Belanja', style: Theme.of(context).textTheme.headline6),
              ReactiveFormArray(
                formArrayName: 'items',
                builder: (context, formArray, child) {
                  return Column(
                    children: formArray.controls.asMap().entries.map((entry) {
                      final index = entry.key;
                      final itemFormGroup = entry.value;
                      return Padding(
                        padding: const EdgeInsets.all(8.0),
                        child: Row(
                          children: [
                            Expanded(
                              child: ReactiveTextField(
                                formControlName: 'namaProduk',
                                decoration: InputDecoration(labelText: 'Produk #${index + 1}'),
                                validationMessages: {
                                  ValidationMessage.required: (_) => 'Nama produk wajib diisi',
                                },
                                formControl: itemFormGroup.control('namaProduk') as FormControl,
                              ),
                            ),
                            const SizedBox(width: 10),
                            SizedBox(
                              width: 80,
                              child: ReactiveTextField(
                                formControlName: 'kuantitas',
                                keyboardType: TextInputType.number,
                                decoration: const InputDecoration(labelText: 'Qty'),
                                validationMessages: {
                                  ValidationMessage.required: (_) => 'Qty wajib',
                                  ValidationMessage.min: (_) => 'Min. 1',
                                },
                                formControl: itemFormGroup.control('kuantitas') as FormControl,
                              ),
                            ),
                            IconButton(
                              icon: const Icon(Icons.remove_circle, color: Colors.red),
                              onPressed: () => _removeShoppingItem(index),
                            ),
                          ],
                        ),
                      );
                    }).toList(),
                  );
                },
              ),
              ElevatedButton.icon(
                onPressed: _addShoppingItem,
                icon: const Icon(Icons.add),
                label: const Text('Tambah Item'),
              ),
              const SizedBox(height: 20),
              ElevatedButton(
                onPressed: () {
                  if (form.valid) {
                    print('Form data: ${form.value}');
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(content: Text('Form valid! Data: ${form.value}')),
                    );
                  } else {
                    form.markAllAsTouched();
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(content: Text('Form tidak valid! Periksa input Anda.')),
                    );
                  }
                },
                child: const Text('Kirim Belanja'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Penjelasan Kode

  • Kita membuat FormArray bernama 'items'. Setiap elemen dalam array ini adalah FormGroup yang mewakili satu item belanja.
  • Fungsi _createShoppingItem() adalah helper untuk membuat FormGroup baru untuk setiap item, memastikan setiap item memiliki struktur yang sama (nama produk dan kuantitas).
  • Metode _addShoppingItem() hanya memanggil items.add(_createShoppingItem()) untuk menambahkan FormGroup baru ke FormArray. Ini secara otomatis akan memicu build ulang UI yang terkait.
  • Metode _removeShoppingItem(int index) menggunakan items.removeAt(index) untuk menghapus FormGroup pada indeks tertentu, juga memicu pembaruan UI.
  • Widget ReactiveFormArray digunakan untuk membangun UI berdasarkan FormArray. Di dalam builder-nya, kita mengulang melalui setiap FormGroup dalam formArray.controls dan merender widget input yang sesuai (ReactiveTextField).
  • Penting untuk dicatat penggunaan formControl: itemFormGroup.control('namaProduk') as FormControl saat berada di dalam ReactiveFormArray karena ReactiveTextField perlu tahu FormControl mana yang harus diikat dalam konteks FormGroup spesifik item tersebut.

Validasi dalam Form Dinamis

Reactive Forms secara otomatis menangani validasi untuk form dinamis Anda. Ketika Anda menambahkan atau menghapus item dari FormArray, status validasi dari FormArray (dan juga FormGroup induknya) akan diperbarui secara reaktif.

  • Anda dapat menambahkan validator ke setiap FormControl saat membuatnya (seperti Validators.required atau Validators.min(1)).
  • Validator juga dapat ditambahkan ke FormGroup atau FormArray itu sendiri (misalnya, validator yang membandingkan dua bidang dalam satu FormGroup).
  • Metode form.markAllAsTouched() sangat berguna untuk memicu pesan validasi pada semua bidang ketika pengguna mencoba mengirim form yang tidak valid.

Keunggulan dan Praktik Terbaik

  • Separasi Tanggung Jawab: Form model terpisah dari UI, membuat kode lebih bersih dan mudah dikelola.
  • Reaktivitas Penuh: Perubahan pada model form secara otomatis tercermin di UI, dan sebaliknya.
  • Pengujian Unit yang Mudah: Logika validasi dan manajemen state form dapat diuji secara terpisah dari UI.
  • Performa: Gunakan ReactiveFormArray dengan bijak. Untuk daftar yang sangat panjang, pertimbangkan untuk mengoptimalkan rendering dengan ListView.builder jika memungkinkan, meskipun ReactiveFormArray sendiri cukup efisien.
  • Ekstensibilitas: Reactive Forms mendukung validator kustom dan widget UI kustom, memungkinkan Anda untuk memperluas fungsionalitasnya sesuai kebutuhan.

Kesimpulan

Reactive Forms adalah alat yang sangat ampuh untuk membangun form di Flutter, terutama ketika berhadapan dengan kompleksitas form dinamis. Dengan menyediakan pendekatan deklaratif dan reaktif, ia menyederhanakan manajemen state, validasi, dan interaksi UI, menghasilkan kode yang lebih mudah dibaca, diuji, dan dipelihara. Jika aplikasi Anda membutuhkan form yang fleksibel dan interaktif, Reactive Forms patut menjadi pertimbangan utama Anda.

Related Articles

Nov 21, 2025

Membangun Aplikasi Flutter yang Efisien dengan State Management Provider

Membangun Aplikasi Flutter yang Efisien dengan State Management Provider Flutter telah merevolusi pengembangan aplikasi mobile dengan kemampuannya untuk memban

Nov 21, 2025

Tips Optimasi Performa Flutter untuk Aplikasi Mobile

Tips Optimasi Performa Flutter untuk Aplikasi Mobile Performa adalah salah satu faktor krusial yang menentukan keberhasilan dan pengalaman pengguna dalam aplik

Nov 22, 2025

Integrasi API REST di Flutter: Pendekatan Modern dengan Dio dan Provider

Integrasi API REST di Flutter: Pendekatan Modern dengan Dio dan Provider Dalam pengembangan aplikasi modern, kemampuan untuk berinteraksi dengan layanan backen