Field teks dan form

Menerima masukan teks dengan TextField, TextEditingController, validasi form, dan pengelolaan fokus.

Daftar isi

Topik ini membahas cara menerima masukan teks di Flutter, mulai dari TextField dengan callback onChanged, pengelolaan nilai melalui TextEditingController, validasi menggunakan Form, hingga perpindahan fokus dengan FocusNode. Materinya diterapkan pada formulir penambahan catatan yang memvalidasi masukan sebelum disimpan.

TextField dan callback onChanged#

TextField adalah widget masukan teks bawaan Material. Setiap kali teks berubah, callback onChanged dipanggil dengan nilai terbaru, sehingga aplikasi dapat merespons setiap ketikan tanpa perantara. Penampilannya diatur melalui properti decoration, misalnya labelText untuk label, hintText untuk teks petunjuk, dan OutlineInputBorder untuk garis tepi. Properti keyboardType menentukan jenis keyboard yang muncul, sedangkan obscureText menyembunyikan karakter untuk masukan sandi.

TextField mengelola teks yang sedang diketik di dalam state internalnya. Ketikan tidak menimbulkan rebuild pada widget induk karena field menggambar ulang tampilannya secara mandiri. Desain ini membuat pengetikan terasa langsung tanpa membebani pohon widget di atasnya.

dart TextField dengan label dan reaksi onChanged
// tidak-dijalankan
TextField(
  onChanged: (nilai) {
    print('Teks sekarang: $nilai');
  },
  decoration: const InputDecoration(
    labelText: 'Judul catatan',
    hintText: 'Tulis judul di sini',
    border: OutlineInputBorder(),
  ),
)

Callback onChanged cukup digunakan selama nilai hanya diperlukan sebagai reaksi atas ketikan. Apabila nilai perlu dibaca pada waktu lain, misalnya saat tombol simpan ditekan, pendekatan berikutnya lebih tepat.

TextEditingController#

TextEditingController adalah controller, yaitu objek yang memegang kendali atas nilai sebuah widget masukan. Melalui properti text, nilai teks dapat dibaca maupun diatur dari kode, misalnya mengosongkan field setelah data disimpan. Controller juga menerima listener sehingga perubahan teks dapat diamati dari luar TextField.

Siklus kerja formulir catatan memperlihatkan perannya. Nilai teks dibaca melalui _kontrolJudul.text saat tombol simpan ditekan, lalu field dikosongkan melalui _kontrolJudul.clear() agar siap menampung catatan berikutnya. Listener yang dipasang melalui addListener dapat memperbarui indikator jumlah karakter setiap kali teks berubah. Perilaku semacam ini tidak dapat dicapai dengan onChanged saja karena nilai perlu diatur ulang dari kode.

Controller dibuat sebagai field kelas State, dipasang pada TextField melalui properti controller, dan wajib dibuang pada metode dispose. Pemanggilan dispose melepaskan sumber daya yang digunakan controller. Kegagalan membuangnya meninggalkan memory leak, yaitu memori yang tidak pernah dilepaskan, dan dapat memicu error ketika controller digunakan setelah widget pemiliknya hilang dari pohon.

dart Siklus hidup TextEditingController pada kelas State
// tidak-dijalankan
class _FormulirCatatanState extends State<FormulirCatatan> {
  final _kontrolJudul = TextEditingController();

  @override
  void dispose() {
    _kontrolJudul.dispose();
    super.dispose();
  }
}

Form dan validator#

Form adalah widget penampung yang mengelompokkan beberapa field masukan sebagai satu kesatuan. Validasi memanfaatkan validator, yaitu fungsi yang memeriksa nilai setiap field dan mengembalikan null apabila valid atau pesan error apabila tidak. Agar state Form dapat diakses dari luar, Form diberi GlobalKey<FormState>, yakni GlobalKey, kunci unik yang memungkinkan akses langsung ke state sebuah widget dari luar pohon.

Field di dalam form menggunakan TextFormField, yaitu TextField yang sudah terikat pada form dan menyediakan properti validator serta onSaved. Pemanggilan validate() pada state Form menjalankan seluruh validator dan menampilkan pesan error pada field yang gagal. Apabila semuanya valid, FormState.save() memanggil onSaved pada setiap field untuk mengumpulkan nilainya.

dart Formulir catatan dengan validasi judul
// tidak-dijalankan
final _formKey = GlobalKey<FormState>();
String? _judulTersimpan;

Form(
  key: _formKey,
  child: Column(
    children: [
      TextFormField(
        controller: _kontrolJudul,
        validator: (nilai) {
          if (nilai == null || nilai.isEmpty) {
            return 'Judul tidak boleh kosong';
          }
          return null;
        },
        onSaved: (nilai) {
          _judulTersimpan = nilai;
        },
      ),
      FilledButton(
        onPressed: _simpan,
        child: const Text('Simpan'),
      ),
    ],
  ),
)

Metode _simpan pada contoh di atas hanya memanggil validasi dan penyimpanan. Penyimpanan berlangsung hanya apabila seluruh validator mengembalikan null.

dart Validasi sebelum menyimpan
// tidak-dijalankan
void _simpan() {
  if (_formKey.currentState!.validate()) {
    _formKey.currentState!.save();
  }
}

Pesan yang dikembalikan validator ditampilkan di bawah field terkait secara otomatis. Validasi yang lebih rinci dapat menggabungkan beberapa aturan pada satu validator, misalnya memeriksa panjang minimum terlebih dahulu, baru panjang maksimum. Dengan cara ini, aturan tetap berada di satu tempat, sedangkan tampilan pesannya konsisten untuk seluruh field dalam formulir.

Memindahkan fokus dengan FocusNode#

Fokus menentukan field mana yang sedang menerima ketikan keyboard. FocusNode adalah objek yang mewakili satu titik fokus, dan pemindahan fokus dilakukan dengan memanggil requestFocus pada node yang dituju. Pola yang lazim adalah perpindahan otomatis dari field judul ke field isi setelah tombol aksi pada keyboard ditekan.

Properti textInputAction mengatur label tombol aksi pada keyboard, misalnya TextInputAction.next untuk tombol berikutnya. Callback onSubmitted menangkap penekanan tombol tersebut, lalu memindahkan fokus ke node berikutnya.

Sebaliknya, fokus dapat dilepaskan melalui FocusScope.of(context).unfocus() agar keyboard ditutup setelah penyimpanan. Kombinasi keduanya membentuk alur pengisian yang runtut. Detail perpindahan fokus ini terlihat kecil, tetapi menentukan kenyamanan formulir yang memiliki lebih dari dua field.

dart Memindahkan fokus dari field judul ke field isi
// tidak-dijalankan
final _fokusJudul = FocusNode();
final _fokusIsi = FocusNode();

TextFormField(
  focusNode: _fokusJudul,
  textInputAction: TextInputAction.next,
  onSubmitted: (_) {
    _fokusIsi.requestFocus();
  },
)

Node fokus juga wajib dibuang pada metode dispose, sama seperti controller.

Kesalahan umum#

  • Lupa membuang controller dan FocusNode. Memory leak menumpuk setiap kali layar formulir dibuka dan ditutup. Perbaikannya, panggil dispose pada setiap objek dari dalam metode dispose kelas State.
  • Memanggil validate() ketika kunci belum terpasang pada Form. _formKey.currentState bernilai null sehingga operator ! melempar error. Pastikan kunci yang sama diberikan pada properti key milik Form.
  • Validator yang tidak mengembalikan nilai pada jalur valid. Analisis Dart melaporkan error karena ada jalur yang tidak mengembalikan apa pun. Kembalikan null di akhir validator untuk jalur yang valid.

Ringkasan#

  • onChanged merespons ketikan secara langsung, sedangkan TextEditingController membaca dan mengatur teks dari kode.
  • Controller dan FocusNode wajib dibuang pada dispose untuk mencegah memory leak.
  • Form dengan GlobalKey<FormState>, validator, dan onSaved menyatukan validasi beberapa field dalam satu panggilan.
  • FocusNode dan textInputAction memindahkan fokus antarfield sesuai alur pengisian.

Referensi#