← Kembali ke Portfolio
Praktikum Aplikasi Mobile

Laporan Praktikum 1
Input Widgets & Basic Form (TextField, TextFormField, Validasi)

Sasya Zamora  ·  2411533014  ·  Informatika – Universitas Andalas  ·  2025/2026

Dosen Pengampu: Nurfiah, S.ST., M.Kom

1 Pendahuluan

Setelah sebelumnya mempelajari widget dasar seperti Text, Container, Row, dan Column, praktikum kali ini masuk ke bagian yang lebih interaktif, yaitu form inputan. Pada dunia aplikasi mobile, hampir semua aplikasi memiliki form, entah itu form login, form pencarian, atau form pendaftaran. Karena itu penting untuk memahami cara Flutter menangani inputan dari keyboard, mulai dari widget paling sederhana (TextField) sampai widget yang sudah bisa melakukan validasi otomatis (TextFormField).

Perbedaan mendasar antara TextField dan TextFormField terletak pada validasi. TextField hanya menangkap teks yang diketik pengguna, sedangkan TextFormField sudah terhubung dengan sistem Form dan dapat diberi aturan validasi lewat parameter validator. Supaya validasi dari beberapa TextFormField bisa dicek sekaligus, dibutuhkan GlobalKey<FormState> yang dipasang ke widget Form, lalu dipanggil lewat _formKey.currentState!.validate() saat tombol submit ditekan.

2 Tujuan Praktikum

Setelah menyelesaikan praktikum ini, mahasiswa diharapkan mampu:

  • Membuat form inputan sederhana menggunakan widget TextField.
  • Membuat form inputan yang sudah dilengkapi validasi menggunakan TextFormField, GlobalKey, dan FormState.
  • Mengambil nilai inputan dari pengguna memakai TextEditingController dan menampilkannya lewat SnackBar.
  • Menerapkan konsep di atas untuk membuat form registrasi dengan banyak field sekaligus (tugas Kelas B).
3 Langkah-Langkah Praktikum
1 Latihan 1 — Basic Form dengan TextField

Langkah pertama, dibuat file baru bernama form_textfield.dart di dalam folder lib. File ini berisi satu StatefulWidget bernama MyForm yang memiliki satu TextField untuk input nama dan satu ElevatedButton untuk menampilkan hasil inputan lewat SnackBar.

import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('Basic Form')), body: const MyForm(), ), ); } } class MyForm extends StatefulWidget { const MyForm({super.key}); @override State<MyForm> createState() => _MyFormState(); } class _MyFormState extends State<MyForm> { TextEditingController _textEditingController = TextEditingController(); @override void dispose() { _textEditingController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(20.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Masukkan nama anda :'), const SizedBox(height: 10), TextField( decoration: const InputDecoration( labelText: 'Nama Lengkap', hintText: 'Misalnya masnoer', border: OutlineInputBorder(), prefixIcon: Icon(Icons.person), ), controller: _textEditingController, keyboardType: TextInputType.text, onChanged: (text) { print('Sedang mengetik teks : $text'); }, ), const SizedBox(height: 20), ElevatedButton( onPressed: () { String inputText = _textEditingController.text; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Nama anda adalah $inputText')), ); }, child: const Text('Tampilkan nama'), ), ], ), ); } }

Karena project sudah memiliki beberapa file dart, menjalankan file ini tidak bisa langsung menekan Run biasa, melainkan harus menentukan target file secara spesifik lewat terminal dengan perintah berikut, supaya Flutter tahu file mana yang dijalankan sebagai entry point.

$ flutter run -t lib/form_textfield.dart -d chrome

Hasilnya:

Gambar 1. Proses menjalankan form_textfield.dart, Flutter sedang menunggu koneksi ke Chrome.
Gambar 1. Proses menjalankan form_textfield.dart, Flutter sedang menunggu koneksi ke Chrome.

Setelah aplikasi terbuka di Chrome, kolom Nama Lengkap diisi dan tombol "Tampilkan nama" ditekan. Hasilnya, teks yang diketik langsung muncul di SnackBar pada bagian bawah layar, menandakan TextEditingController berhasil mengambil nilai inputan dengan benar.

Gambar 2. Hasil form_textfield setelah nama diisi dan tombol ditekan, SnackBar menampilkan nama yang diinput.
Gambar 2. Hasil form_textfield setelah nama diisi dan tombol ditekan, SnackBar menampilkan nama yang diinput.
2 Latihan 2 — Basic Form dengan TextFormField dan Validasi

Selanjutnya dibuat file form_textformfield.dart. Bedanya dengan latihan sebelumnya, form ini memiliki dua TextFormField (Nama dan Email) yang dibungkus widget Form dan diberi validator masing-masing. Validator email juga mengecek apakah input mengandung karakter @; jika tidak, maka dianggap tidak valid.

class _MyFormTextState extends State<MyFormText> { final _formKey = GlobalKey<FormState>(); final _nameController = TextEditingController(); final _emailController = TextEditingController(); void _submitForm() { if (_formKey.currentState!.validate()) { String name = _nameController.text; String email = _emailController.text; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Validasi $name, $email Berhasil'))); } } @override Widget build(BuildContext context) { return Form( key: _formKey, child: Padding( padding: const EdgeInsets.all(20.0), child: Column(children: [ TextFormField( controller: _nameController, decoration: const InputDecoration(labelText: 'Nama : '), validator: (value) => (value == null || value.isEmpty) ? 'Masukkan nama anda' : null, ), TextFormField( controller: _emailController, decoration: const InputDecoration(labelText: 'Email : '), validator: (value) { if (value == null || value.isEmpty) return 'Masukkan email anda'; if (!value.contains('@')) return 'Email tidak valid'; return null; }, ), ElevatedButton(onPressed: _submitForm, child: const Text('Submit')), ]), ), ); } }

Hasilnya:

Gambar 3. Tampilan awal form_textformfield sebelum diisi, terdiri dari kolom Nama dan Email.
Gambar 3. Tampilan awal form_textformfield sebelum diisi, terdiri dari kolom Nama dan Email.
Gambar 4. Proses build ulang dan menjalankan form_textformfield.dart lewat terminal.
Gambar 4. Proses build ulang dan menjalankan form_textformfield.dart lewat terminal.

Kolom Nama dan Email kemudian diisi dengan data yang valid, lalu tombol Submit ditekan. Karena validator tidak menemukan kesalahan (value tidak kosong dan email mengandung '@'), method _formKey.currentState!.validate() mengembalikan nilai true sehingga _submitForm() lanjut menampilkan SnackBar berisi data yang berhasil divalidasi.

Gambar 5. Struktur kode form_textformfield.dart dengan GlobalKey dan FormState.
Gambar 5. Struktur kode form_textformfield.dart dengan GlobalKey dan FormState.
Gambar 6. Form berhasil divalidasi, SnackBar menampilkan nama dan email yang diinput.
Gambar 6. Form berhasil divalidasi, SnackBar menampilkan nama dan email yang diinput.
3 Tugas Kelas B — Form Registrasi Pengguna

Tugas untuk Kelas B adalah membuat form registrasi dengan 4 input (Nama, Email, Password, dan Confirm Password) yang dipisah menjadi dua file, yaitu user_registration_form.dart untuk tampilan form dan main_registration.dart sebagai entry point yang memanggil widget tersebut.

Pada user_registration_form.dart dibuat StatefulWidget UserRegistrationForm dengan 4 buah TextEditingController dan validator masing-masing: Nama tidak boleh kosong, Email harus mengandung '@', Password minimal 6 karakter, dan Confirm Password harus sama persis dengan Password yang diketik sebelumnya.

class _UserRegistrationFormState extends State<UserRegistrationForm> { final _formKey = GlobalKey<FormState>(); final _nameController = TextEditingController(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); final _confirmPasswordController = TextEditingController(); void _submitForm() { if (_formKey.currentState!.validate()) { String name = _nameController.text; String email = _emailController.text; ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text('Registrasi $name ($email) berhasil!'))); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Form Registrasi Pengguna')), body: Form( key: _formKey, child: Padding( padding: const EdgeInsets.all(20.0), child: Column(children: [ TextFormField( controller: _nameController, decoration: const InputDecoration(labelText: 'Nama : '), validator: (v) => (v == null || v.isEmpty) ? 'Masukkan nama anda' : null), TextFormField( controller: _emailController, decoration: const InputDecoration(labelText: 'Email : '), validator: (v) { if (v == null || v.isEmpty) return 'Masukkan email anda'; if (!v.contains('@')) return 'Email tidak valid'; return null; }), TextFormField( controller: _passwordController, obscureText: true, decoration: const InputDecoration(labelText: 'Password : '), validator: (v) { if (v == null || v.isEmpty) return 'Masukkan password anda'; if (v.length < 6) return 'Password minimal 6 karakter'; return null; }), TextFormField( controller: _confirmPasswordController, obscureText: true, decoration: const InputDecoration(labelText: 'Confirm Password : '), validator: (v) { if (v == null || v.isEmpty) return 'Masukkan ulang password anda'; if (v != _passwordController.text) return 'Password tidak sama'; return null; }), ElevatedButton(onPressed: _submitForm, child: const Text('Submit')), ]), ), ), ); } }
Gambar 7. Isi main_registration.dart yang memanggil UserRegistrationForm sebagai halaman utama.
Gambar 7. Isi main_registration.dart yang memanggil UserRegistrationForm sebagai halaman utama.
Gambar 8. Struktur kode user_registration_form.dart dengan 4 controller dan validator masing-masing field.
Gambar 8. Struktur kode user_registration_form.dart dengan 4 controller dan validator masing-masing field.

File dijalankan dengan perintah berikut:

$ flutter run -t lib/main_registration.dart -d chrome

Hasilnya:

Gambar 9. Proses menjalankan main_registration.dart, sempat berpindah dari sesi form_textformfield sebelumnya.
Gambar 9. Proses menjalankan main_registration.dart, sempat berpindah dari sesi form_textformfield sebelumnya.

Tampilan awal form menunjukkan 4 kolom kosong yaitu Nama, Email, Password, dan Confirm Password, ditambah satu tombol Submit di bagian bawah.

Gambar 10. Tampilan awal Form Registrasi Pengguna sebelum data diisi.
Gambar 10. Tampilan awal Form Registrasi Pengguna sebelum data diisi.

Setelah keempat kolom diisi dengan data yang sesuai aturan validasi (nama tidak kosong, email mengandung '@', password minimal 6 karakter, dan confirm password sama dengan password), tombol Submit ditekan. Karena seluruh validator mengembalikan nilai null (artinya tidak ada error), _submitForm() berhasil dijalankan dan menampilkan SnackBar konfirmasi registrasi berhasil.

Gambar 11. Form Registrasi Pengguna terisi lengkap, SnackBar menampilkan pesan registrasi berhasil.
Gambar 11. Form Registrasi Pengguna terisi lengkap, SnackBar menampilkan pesan registrasi berhasil.
4 Kesimpulan

Dari praktikum ini dapat dilihat bahwa TextField dan TextFormField sebenarnya mirip, tetapi berbeda kegunaan. TextField cukup untuk inputan yang sederhana dan tidak perlu dicek, sedangkan TextFormField lebih tepat dipakai ketika inputan harus divalidasi terlebih dahulu sebelum datanya diproses lebih jauh, seperti pada Latihan 2 dan tugas Kelas B. Validasi sebanyak apa pun fieldnya menjadi lebih mudah ditangani berkat GlobalKey<FormState> yang dipasang ke widget Form, karena cukup memanggil _formKey.currentState!.validate() sekali saja untuk mengecek semua field sekaligus, dibandingkan harus mengecek satu per satu secara manual yang lebih rumit dan rawan salah.

Selain itu, TextEditingController juga menjadi bagian yang tidak kalah penting, karena lewat controller ini nilai yang diketik pengguna bisa diambil, baik hanya untuk ditampilkan langsung seperti pada Latihan 1 maupun untuk diolah dulu sebelum ditampilkan seperti pada form registrasi Kelas B. Konsep validasi dua field pada Latihan 2 juga dapat dikembangkan menjadi lebih kompleks, contohnya pada form registrasi yang memiliki empat field sekaligus dengan validasi antarfield, yaitu password dan confirm password yang harus sama persis. Satu hal lagi yang baru disadari selama praktikum: karena dalam satu project terdapat banyak file dart untuk latihan yang berbeda, menjalankan file tertentu tidak bisa asal klik Run, melainkan harus disebutkan targetnya lewat perintah flutter run -t <nama_file> -d chrome supaya Flutter tidak salah menjalankan main.dart bawaan project.