LAPORAN PRAKTIKUM APLIKASI MOBILE

PRAKTIKUM 1: Input Widgets dan Basic Form pada Flutter

NIM: 2411533019 - Adila Bintang Fauzi

Flutter / Mobile App Repository GitHub

1. Pendahuluan

Pengembangan aplikasi mobile modern menuntut pengelolaan interaksi pengguna yang responsif, terstruktur, dan tervalidasi dengan baik. Pada framework Flutter, penanganan input dari pengguna dilakukan melalui komponen formulir dasar (Basic Form) dan Input Widgets, seperti TextField, TextFormField, CheckBox, Switch, Dropdown, Radio, DatePicker, hingga komponen antarmuka dialog pendukung seperti BottomSheet dan SnackBar. Komponen-komponen ini berfungsi menangkap data, menampilkan field input, memvalidasi format data secara akurat, serta mengambil nilai inputan untuk diproses pada tingkat logika aplikasi.

Dalam praktik pengembangannya, Flutter menyediakan dua komponen utama untuk menangani input teks, yaitu TextField dan TextFormField. TextField digunakan untuk penangkapan teks sederhana secara langsung dari keyboard dengan pengelolaan state mandiri via TextEditingController, di mana validasi dan pesan kesalahan harus ditangani secara manual. Sebaliknya, TextFormField merupakan widget pembungkus TextField yang terintegrasi langsung dengan logika Form. Komponen ini dilengkapi dengan properti bawaan validator untuk memeriksa aturan input secara otomatis dan menampilkan pesan kesalahan di bawah field jika data tidak sesuai, serta dapat dikontrol secara terpusat menggunakan FormState.

Pengelolaan formulir yang efisien di Flutter ditopang oleh beberapa komponen kunci. TextEditingController digunakan untuk mengontrol dan membaca teks pada input field, yang mana wajib dibersihkan melalui metode dispose() untuk mencegah kebocoran memori (memory leak). Untuk mengidentifikasi dan mengakses status dari Form dari luar widget, digunakan GlobalKey<FormState>. Status ini menyediakan fungsi validate() yang mengeksekusi seluruh validator TextFormField di dalam Form secara kolektif—menghasilkan nilai true jika seluruh data valid atau false jika terdapat kesalahan. Selain itu, pembaruan antarmuka saat terjadi perubahan state ditangani oleh metode setState(), sedangkan optimasi memori dan performa reloading aplikasi dicapai dengan menerapkan kata kunci const pada widget konstan saat kompilasi.

2. Tujuan Praktikum

  • Membuat dan mengonfigurasi berbagai jenis Input Widgets pada Flutter.
  • Mengontrol dan membaca nilai input pengguna menggunakan TextEditingController.
  • Menerapkan mekanisme validasi data menggunakan TextFormField, FormState, dan GlobalKey.
  • Memahami pengelolaan siklus hidup widget, pelepasan memori (metode dispose()), serta peremajaan antarmuka menggunakan setState().

3. Langkah-Langkah Praktikum

3.1 Implementation Basic Form dengan TextField

Berikut merupakan kode program pembuatan form dasar menggunakan TextField untuk mengambil input nama dan menampilkannya pada notifikasi SnackBar:

// NIM: 2411533019 - Adila Bintang Fauzi
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> {
  final 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(
            controller: _textEditingController,
            keyboardType: TextInputType.text,
            decoration: const InputDecoration(
              labelText: 'Nama Lengkap',
              hintText: 'Misalnya masnoer',
              border: OutlineInputBorder(),
              prefixIcon: Icon(Icons.person),
            ),
            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')),
              );
            },
            style: ElevatedButton.styleFrom(
              backgroundColor: Colors.amber,
              foregroundColor: Colors.black,
            ),
            child: const Text('Tampilkan nama'),
          ),
        ],
      ),
    );
  }
}

Penjelasan Kode & Alur Kerja:

  • TextEditingController: Variabel _textEditingController dibuat untuk menangkap dan memantau isi input field secara langsung. Pada method dispose(), controller dibersihkan untuk mencegah kebocoran memori (memory leak).
  • TextField Widget: Memiliki properti decoration: InputDecoration(...) dengan OutlineInputBorder() dan ikon Icons.person agar tampilan input lebih komunikatif. Event onChanged digunakan untuk memantau setiap karakter yang diketik oleh pengguna melalui konsol terminal.
  • Aksi Tombol Submit: Ketika ElevatedButton ditekan, aplikasi mengambil nilai teks melalui properti _textEditingController.text lalu menampilkannya sebagai pesan notifikasi melayang via ScaffoldMessenger.of(context).showSnackBar().
Tampilan Kode Program di Editor (form_textfield.dart)
Kode Program form_textfield.dart
Hasil Pengujian Basic Form TextField

Tampilan form saat input nama "lala" dimasukkan dan tombol "Tampilkan mama" ditekan, memunculkan SnackBar di bagian bawah:

Hasil Running Basic Form TextField
3.2 Implementasi Form Validasi dengan TextFormField

Berikut merupakan kode program terstruktur menggunakan widget Form, GlobalKey<FormState>, serta mekanisme validasi input Nama dan Email:

// NIM: 2411533019 - Adila Bintang Fauzi
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 TextFormField")),
        body: const MyFormText(),
      ),
    );
  }
}

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

  @override
  State<MyFormText> createState() => _MyFormTextState();
}

class _MyFormTextState extends State<MyFormText> {
  final _formKey = GlobalKey<FormState>();
  final _nameController = TextEditingController();
  final _emailController = TextEditingController();

  @override
  void dispose() {
    _nameController.dispose();
    _emailController.dispose();
    super.dispose();
  }

  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(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const SizedBox(height: 10),
            TextFormField(
              controller: _nameController,
              decoration: const InputDecoration(
                labelText: "Nama",
                border: OutlineInputBorder(),
              ),
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Masukkan nama anda';
                }
                return null;
              },
            ),
            const SizedBox(height: 10),
            TextFormField(
              controller: _emailController,
              decoration: const InputDecoration(
                labelText: "Email",
                border: OutlineInputBorder(),
              ),
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Masukkan email anda';
                }
                if (!value.contains('@')) {
                  return 'Email tidak valid';
                }
                return null;
              },
            ),
            const SizedBox(height: 10),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: _submitForm,
                child: const Text('Submit'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Penjelasan Validasi & Alur Program:

  • GlobalKey<FormState>: Kunci global _formKey digunakan sebagai identitas unik widget Form, memungkinkan akses method validate() dari luar pohon widget.
  • Fungsi Validator: Setiap TextFormField memiliki parameter callback validator:
    • Field Nama: Mengecek apakah teks kosong (value == null || value.isEmpty). Jika kosong, mengembalikan string error 'Masukkan nama anda'.
    • Field Email: Selain mengecek kekosongan nilai, juga memvalidasi sintaks dengan !value.contains('@') untuk memastikan adanya tanda '@'. Jika tidak ada, mengembalikan pesan error 'Email tidak valid'.
  • Metode _submitForm(): Ketika tombol Submit diklik, method _formKey.currentState!.validate() dieksekusi. Apabila seluruh field valid (mengembalikan true), maka SnackBar berhasil ditampilkan. Sebaliknya jika ada field yang salah, pesan error otomatis dicetak merah di bawah masing-masing kolom input.
Tampilan Kode Program di Editor (form-textformfield.dart)
Kode Program form-textformfield.dart
Tampilan Antarmuka Form Kosong
Tampilan Form TextFormField Kosong
Hasil Pengujian Validasi Berhasil

Tampilan saat nama "zaki" dan email "zaki@gmail.com" berhasil lolos validasi, memicu SnackBar notifikasi sukses:

Hasil Validasi TextFormField Berhasil

4. Tugas dan Penugasan Praktikum

Tugas Kelas B: Form Pendaftaran Pengguna
Membuat aplikasi Form Pendaftaran Pengguna terstruktur menggunakan Flutter dengan spesifikasi:

  • Memiliki 4 field input: Nama, Email, Password, dan Confirm Password.
  • Memisahkan berkas proyek secara modular menjadi main.dart dan user_registration_form.dart.
  • Aturan Validasi:
    • Seluruh kolom wajib diisi (tidak boleh kosong).
    • Kolom Email wajib mengandung karakter '@'.
    • Kolom Confirm Password harus bernilai sama persis dengan kolom Password.
  • Menggunakan StatefulWidget, manajemen controller, serta pemanggilan method _submitForm() saat tombol submit ditekan.

Source Code Program:

1. main.dart:

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

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Form Pendaftaran Pengguna',
      home: const UserRegistrationForm(),
    );
  }
}

2. user_registration_form.dart:

import 'package:flutter/material.dart';

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

  @override
  State<UserRegistrationForm> createState() => _UserRegistrationFormState();
}

class _UserRegistrationFormState extends State<UserRegistrationForm> {
  final _formKey = GlobalKey<FormState>();

  final _nameController = TextEditingController();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  final _confirmPasswordController = TextEditingController();

  @override
  void dispose() {
    _nameController.dispose();
    _emailController.dispose();
    _passwordController.dispose();
    _confirmPasswordController.dispose();
    super.dispose();
  }

  void _submitForm() {
    if (_formKey.currentState!.validate()) {
      String name = _nameController.text;
      String email = _emailController.text;

      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Pendaftaran $name ($email) Berhasil!')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Form Pendaftaran Pengguna"),
      ),
      body: Form(
        key: _formKey,
        child: Padding(
          padding: const EdgeInsets.all(20.0),
          child: SingleChildScrollView(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.start,
              children: [
                const SizedBox(height: 10),
                // Input Nama
                TextFormField(
                  controller: _nameController,
                  decoration: const InputDecoration(
                    labelText: "Nama : ",
                    border: OutlineInputBorder(),
                  ),
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return 'Masukkan nama anda';
                    }
                    return null;
                  },
                ),
                const SizedBox(height: 10),

                // Input Email
                TextFormField(
                  controller: _emailController,
                  decoration: const InputDecoration(
                    labelText: "Email : ",
                    border: OutlineInputBorder(),
                  ),
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return 'Masukkan email anda';
                    }
                    if (!value.contains('@')) {
                      return 'Email tidak valid (harus mengandung @)';
                    }
                    return null;
                  },
                ),
                const SizedBox(height: 10),

                // Input Password
                TextFormField(
                  controller: _passwordController,
                  obscureText: true,
                  decoration: const InputDecoration(
                    labelText: "Password : ",
                    border: OutlineInputBorder(),
                  ),
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return 'Masukkan password anda';
                    }
                    return null;
                  },
                ),
                const SizedBox(height: 10),

                // Input Confirm Password
                TextFormField(
                  controller: _confirmPasswordController,
                  obscureText: true,
                  decoration: const InputDecoration(
                    labelText: "Confirm Password : ",
                    border: OutlineInputBorder(),
                  ),
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return 'Konfirmasi password anda';
                    }
                    if (value != _passwordController.text) {
                      return 'Password dan Confirm Password tidak sama';
                    }
                    return null;
                  },
                ),
                const SizedBox(height: 20),

                // Tombol Submit
                SizedBox(
                  width: double.infinity,
                  child: ElevatedButton(
                    onPressed: _submitForm,
                    child: const Text('Submit'),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Pembahasan Logika Kode Program Tugas:

  • Modularitas Berkas: File main.dart bertindak murni sebagai entry-point (menginisialisasi MaterialApp) dan memanggil widget terpisah UserRegistrationForm. Hal ini menjaga kode tetap bersih, mudah didebug, dan siap diskalakan.
  • SingleChildScrollView: Membungkus Column formulir dengan SingleChildScrollView mencegah terjadinya pesan error RenderFlex overflowed by X pixels ketika keyboard virtual smartphone muncul dan memperkecil ruang layar.
  • Keamanan Input Password: Mengaktifkan properti obscureText: true pada field password dan confirm password untuk menyamarkan karakter input menjadi titik-titik demi menjaga privasi pengguna.
  • Validasi Konfirmasi Password: Validator kolom confirm password membandingkan nilainya secara langsung dengan controller password (value != _passwordController.text). Apabila string kedua input tidak identik, sistem menolak submit dan memunculkan error peringatan.
1. Screenshot Kode Program main.dart
Kode Program main.dart
2. Screenshot Kode Program user_registration_form.dart (Bagian Atas & Logika)
Kode Program user_registration_form.dart Bagian 1
3. Screenshot Kode Program user_registration_form.dart (Bagian Input Fields & Tombol)
Kode Program user_registration_form.dart Bagian 2
Hasil Akhir Pengujian

Tampilan antarmuka Form Pendaftaran Pengguna yang diisi lengkap beserta notifikasi SnackBar "Pendaftaran zaki (zaki@gmail.com) Berhasil!":

Hasil Akhir Form Pendaftaran Flutter

5. Kesimpulan

Berdasarkan praktikum yang telah dilaksanakan mengenai Input Widgets dan Basic Form pada Flutter, dapat ditarik beberapa kesimpulan penting sebagai berikut:

  1. Diferensiasi TextField dan TextFormField: TextField sangat efektif untuk penanganan input teks sederhana dan independen. Sementara TextFormField merupakan solusi ideal untuk formulir terstruktur karena langsung terintegrasi dengan widget Form dan menyediakan parameter validator bawaan untuk validasi deklaratif.
  2. Kekuatan GlobalKey dalam Validasi Kolektif: Penggunaan GlobalKey<FormState> memungkinkan kontrol terpusat terhadap seluruh form. Cukup dengan satu pemanggilan method _formKey.currentState!.validate(), seluruh validator di dalam form akan serempak dieksekusi tanpa perlu validasi manual berulang kali.
  3. Pencegahan Kebocoran Memori (Memory Leak): Setiap instance TextEditingController wajib di-dispose secara eksplisit di dalam lifecycle method dispose() pada StatefulWidget untuk memastikan resource memori perangkat dilepas secara bersih saat widget tidak lagi digunakan.
  4. Keamanan & Pengalaman Pengguna (UX): Implementasi properti seperti obscureText: true memberikan keamanan esensial untuk input kata sandi, sedangkan penggunaan SingleChildScrollView mencegah terjadinya bottom overflow error saat keyboard virtual perangkat lunak aktif.
  5. Struktur Kode Bersih & Modular (Clean Code): Memisahkan logika antarmuka ke dalam beberapa berkas terpisah (seperti main.dart dan user_registration_form.dart) membuat struktur aplikasi lebih rapi, modular, mudah dibaca, serta memudahkan perawatan (maintainability) pada proyek skala besar.