Laporan Praktikum Aplikasi Mobile Pertemuan 3

Tujuan praktikum ini yaitu mahasiswa mampu membuat membuat basic form untuk menerima inputan dari keyboard dan mengelola inputan:

  • Membuat beberapa input widgets
  • Membuat dan mengontrol inputan dari user
  • Basic Form

Basic Form merupakan widget yang berfungsi sebagai inputan nilai seperti TextField, TextFormField, CheckBox, Switch, Dropdown, Radio, Dialog, DatePicker, BottomSheet, Snackbar dan lain-lain. Basic Form digunakan untuk validasi dan mengelola inputan dari berbagai field. Form akan memberikan tampilan inputan kemudian inputan akan diperiksa apakah sudah sesuai dengan aturan atau format yang ditetapkan, selanjtunya data inputan akan diambil nilainya setelah proses pengecekan selesai dilakukan.

  • Text Field

TextField adalah widget yang digunakan untuk memasukkan text oleh pengguna, widget ini biasanya digunakan untuk membuat form inputan seperti form login, pencarian dll.

Fitur TextField:

  • Menerima input dari keyboard
  • Memiliki property yang lengkap style, decoration, dan jenis inputan
  • Dapat mengelola teks menggunakan TextEditingController
  • TextFormField

TextFormField adalah widget versi lengkap dari TextField yang secara otomatis terintegrasi dengan logika validasi dan manajemen state dari sebuah form.

Fitur TextFormField:

  • Menerima input teks dari keyboard.
  • Memiliki properti validator yang berfungsi untuk memeriksa apakah input sudah sesuai dengan aturan yang ditentukan.
  • Menampilkan pesan error secara otomatis di bawah field jika validasi gagal.
  • Berinteraksi dengan FormState untuk melakukan validasi secara kolektif dengan validate() method.
  • GlobalKey<FormState>

GlobalKey merupakan objek unik atau key yang digunakan untuk mengidentifikasi dan mengakses state secara global, artinya kita dapat mengakses widget dari widget mana saja.

  • FormState.
    • FormState adalah kelas yang mengelola status dari Form, sepert status validasi setiap form inputan (TextFormField).
    • Menggunakan GlobalKey pada widget Form, maka kita dapat memanggil metode seperti validate() atau save() dari luar widget tersebut, biasanya dari onPressed pada ElevatedButton.
  • validate()

Metode validate() merupakan sebuah fungsi yang terdapat pada FormState yang digunakan untuk menjalankan validasi pada setiap TextFormField yang ada di dalam Form. Ketika Anda memanggil _formKey.currentState!.validate(), maka Flutter akan:

  • Mengecek setiap TextFormField yang terikat pada Form tersebut.
  • Menjalankan fungsi validator yang telah didefinisikan pada setiap TextFormField.
  • Jika fungsi validator mengembalikan String (pesan error), validate() akan menghentikan proses dan mengembalikan nilai false. Pesan error tersebut akan ditampilkan di bawah TextFormField tersebut.
  • Jika semua fungsi validator mengembalikan null (tidak ada error), validate() akan mengembalikan nilai true.

Sekarang adalah waktunya menerapkan teori diatas dengan langkah-langkah berikut:

1. Basic Form TextField

Buat tampilan basic form dengan menggunakan Widget TextField untuk inputan, ElevatedButton untuk memberikan event listener. Buat tampilan menggunakan kode program berikut :

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> {
  @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,
          ),
          const TextField(
            decoration: InputDecoration(
              labelText: 'Nama Lengkap',
              hintText: 'Misalnya masnoer',
              border: OutlineInputBorder(),
              prefixIcon: Icon(Icons.person),
            ),
          ),
          const SizedBox(
            height: 20,
          ),
          ElevatedButton(
            onPressed: () {},
            style: ElevatedButton.styleFrom(
              backgroundColor: Colors.amber,
              foregroundColor: Colors.black,
            ),
            child: const Text('Tampilkan nama'),
          ),
        ],
      ),
    );
  }
}

Jika dijalankan maka hasilnya sebagai berikut:

Penjelasan Kode:

  1. import dan main(): Mengimpor paket material.dart dan menjalankan aplikasi dengan widget MyApp.
  2. MyApp: Widget StatelessWidget yang mengembalikan MaterialApp dengan Scaffold (termasuk AppBar dan body).
  3. MyForm: Widget StatefulWidget yang menampung logika form, mengembalikan _MyFormState.
  4. _MyFormState: Mengimplementasikan tata letak UI, menggunakan Padding dan Column untuk menampilkan:
    • Text ('Masukkan nama anda :').
    • SizedBox untuk spasi.
    • TextField dengan dekorasi dan ikon.
    • SizedBox untuk spasi.
  5. ElevatedButton dengan styling dan teks ‘Tampilkan nama’.

Jika kita masukkan nama ke textfield dan menekan tombol, maka tampilan belum bisa berubah. Maka dari itu, Pada class _MyFormState tambahkan kode program berikut:

TextEditingController _textEditingController = TextEditingController();
  @override
  void dispose() {
    _textEditingController.dispose();
    super.dispose();
  }

Penjelasan:

  • Line 1 merupakan membuat sebuah variable dari TextEditingController, controller ini berfungsi untuk mengambil inputan dari user.
  • Line 2 – 6 merupakan method yang digunakan untuk membersihan text inputan

Kemudian, tambahkan juga property berikut pada Widget TextField

TextField(
            controller: _textEditingController,
            decoration: const InputDecoration(
              labelText: 'Nama Lengkap',
              hintText: 'Misalnya masnoer',
              border: OutlineInputBorder(),
              prefixIcon: Icon(Icons.person),
            ),
            keyboardType: TextInputType.text,
            onChanged: (text) {
              print('Sedang mengetik teks : $text');
            },

Penjelasan:

  • Line 7 menambahkan controller _textEditingController
  • Line 8 menambahkan property jenis inputan yang boleh diinputkan oleh user
  • Line 9 – 11 method yang digunakan untuk event listener Ketika sedang menginputkan teks

Kemudian, Tambahkan kode program berikut pada widget ElevatedButton

ElevatedButton(
            onPressed: () {
              String inputText = _textEditingController.text;
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('Nama anda adalah: $inputText')),
              );
            },

Penjelasan:

  • Line 2 membuat variable inputText yang berfungsi untuk menampung inputan dari keyboard
  • Line 2 – 5 menampilkan inputan text dengan menggunakan widget SnackBar

Berikut adalah kode full dari widget tersebut:

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,
            decoration: const InputDecoration(
              labelText: 'Nama Lengkap',
              hintText: 'Misalnya masnoer',
              border: OutlineInputBorder(),
              prefixIcon: Icon(Icons.person),
            ),
            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'),
          ),
        ],
      ),
    );
  }
}

Simpan kemudian jalankan maka hasilnya akan seperti berikut:

Kemudian, kita akan membuat Basic Form TextFormField. Buat form inputan menggunakan 2 buah widget TextFormField dan 1 buah ElevatedButton, berikut kode program untuk tampilan tersebut.

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> {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const SizedBox(
          height: 10,
        ),
        TextFormField(
          decoration: const InputDecoration(
            labelText: "Nama :",
            border: OutlineInputBorder(),
          ),
        ),
        const SizedBox(
          height: 10,
        ),
        TextFormField(
          decoration: const InputDecoration(
            labelText: "Email :",
            border: OutlineInputBorder(),
          ),
        ),
        const SizedBox(
          height: 10,
        ),
        SizedBox(
          width: double.infinity,
          child: ElevatedButton(
            onPressed: () {},
            child: const Text('Submit'),
          ),
        ),
      ],
    );
  }
}

Penjelasan:

  1. Struktur Aplikasi (MyApp & Scaffold)
    • MyApp (StatelessWidget): Adalah wadah aplikasi yang menyediakan MaterialApp (untuk tema Material Design).
    • Scaffold: Menyediakan kerangka visual aplikasi, termasuk AppBar (bilah judul) dan body (area konten).
  2. Widget Formulir (MyFormText & _MyFormTextState)
    • MyFormText (StatefulWidget): Widget yang memegang state (data) dari formulir.
    • _MyFormTextState: Kelas state yang membangun UI utama formulir menggunakan Column untuk menyusun elemen secara vertikal.
  3. Elemen UI
    • TextFormField: Digunakan untuk input Nama dan Email. Ini adalah widget input yang ideal untuk formulir karena mendukung validasi.
      • InputDecoration: Memberi styling pada field dengan labelText (label) dan OutlineInputBorder (bingkai).
    • SizedBox: Digunakan untuk memberikan jarak (height: 10) antar elemen input.
    • ElevatedButton: Tombol ‘Submit’. Dibungkus dalam SizedBox(width: double.infinity) agar lebarnya penuh.
      • Properti onPressed: () {} saat ini kosong, artinya tombol belum memiliki fungsi aksi saat diklik.

Jika dijalankan maka akan seperti berikut:

Kode tersebut baru bersifat statis. Artinya, belum bisa diapa-apakan. Tambahkan kode berikut agar kodenya dapat berjalan. Tambahkan pada class _MyFormTextState kode berikut:

final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _emailController = TextEditingController();

@override
void dispose() {
  // Membersihkan (dispose) controller untuk mencegah kebocoran memori.
  _nameController.dispose();
  _emailController.dispose();
  super.dispose();
}

void _submitForm() {
  // Memvalidasi formulir menggunakan GlobalKey.
  // currentStat!.validate() mengembalikan true jika semua TextFormField valid.
  if (_formKey.currentState!.validate()) {
    String name = _nameController.text;
    String email = _emailController.text;

    // Menampilkan SnackBar dengan data yang diambil dari controller
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('Validasi $name, $email Berhasil'))
    );
  }
}

Penjelasan:

  • Line 1 membuat variable GlobalKey
  • Line 2 – 3 membuat controller untuk TextFormField
  • Line 5 – 10 method yang berfungsi untuk menghapus inputan name dan email
  • Line 12 – 21 method yang digunakan Ketika tombol diklik

Kemudian pada widget tampilan, bungkus menggunakan widget Form dan tambahkan key: _formKey, buat seperti berikut:

Kemudian, tambahkan kode berikut pada name dan email text field:

TextFormField(
  controller: _nameController,
  decoration: const InputDecoration(
    labelText: "Nama :",
    border: OutlineInputBorder(),
  ),
  validator: (value) {
    if (value == null || value.isEmpty) {
      return 'Masukkan nama anda';
    }
    return null;
  },
),
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;
  },
),

Selanjutnya tambahkan method _submitForm pada method onPressed di ElevatedButton seperti kode program berikut.

SizedBox(
  width: double.infinity,
  child: ElevatedButton(
    onPressed: _submitForm,
    child: const Text('Submit'),
  ),
)

Simpan dan Run maka akan seperti berikut:

Jika salah akan seperti berikut:

Tugas

  • Buat 2 buah file dart yaitu main.dart dan user_registration_form.dart
  • Gunakan MaterialApp sebagai root pada main.dart
  • Set propreti home ke widget UserRegistrationForm
  • Gunakan statefulwidget pada UserRegistrationForm
  • Gunakan widget TextFormField untuk inputan, gunakan method validador untuk validasi
  • Validasi : inputan email harus menggunakan format @, inputan password dan confirm password harus sama atau valid
  • Gunakan method onPressed dan Ketika di pencet akan memanggil _submitForm

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: 'User Registration',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(primarySwatch: Colors.blue),
      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();

  void _submitForm() {
    if (_formKey.currentState!.validate()) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text(
            "Registrasi berhasil!\nEmail: ${_emailController.text}",
          ),
        ),
      );
    }
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("User Registration")),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              // Name Field
              TextFormField(
                controller: _nameController,
                decoration: const InputDecoration(
                  labelText: "Nama",
                  prefixIcon: Icon(Icons.person),
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return "Nama tidak boleh kosong";
                  } else if (RegExp(r'[0-9]').hasMatch(value)) {
                    return "Format nama tidak valid";
                  }
                  return null;
                },
              ),
              const SizedBox(height: 16),

              // Email Field
              TextFormField(
                controller: _emailController,
                decoration: const InputDecoration(
                  labelText: "Email",
                  prefixIcon: Icon(Icons.email),
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return "Email tidak boleh kosong";
                  } else if (!value.contains('@')) {
                    return "Format email tidak valid";
                  }
                  return null;
                },
              ),
              const SizedBox(height: 16),

              // Password Field
              TextFormField(
                controller: _passwordController,
                decoration: const InputDecoration(
                  labelText: "Password",
                  prefixIcon: Icon(Icons.lock),
                ),
                obscureText: true,
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return "Password tidak boleh kosong";
                  }
                  return null;
                },
              ),
              const SizedBox(height: 16),

              // Confirm Password Field
              TextFormField(
                controller: _confirmPasswordController,
                decoration: const InputDecoration(
                  labelText: "Confirm Password",
                  prefixIcon: Icon(Icons.lock_outline),
                ),
                obscureText: true,
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return "Konfirmasi password tidak boleh kosong";
                  } else if (value != _passwordController.text) {
                    return "Password tidak sama";
                  }
                  return null;
                },
              ),
              const SizedBox(height: 24),

              // Submit Button
              ElevatedButton(
                onPressed: _submitForm,
                child: const Text("Register"),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Penjelasan:

  • user_registration_form.dart

Bagian:

final _formKey = GlobalKey<FormState>();
  final _nameController = TextEditingController();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  final _confirmPasswordController = TextEditingController();

Digunakan untuk deklarasi variabel dan controller awal. _formKey dipakai untuk menghubungkan Form dengan validasi. dan ada 3 controller untuk mengambil isi inputan: email, password, dan konfirmasi password.

Method _submitForm()

void _submitForm() {
  if (_formKey.currentState!.validate()) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(
          "Registrasi berhasil!\nEmail: ${_emailController.text}",
        ),
      ),
    );
  }
}

Method ini berfungsi untuk memanggil validate()dimana Kalau semua valid, akan menampilkan pesan sukses menggunakan SnackBar kemudian akan menjalankan semua validator pada TextFormField.

Method dispose()

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

dispose() dipakai untuk membersihkan resource dari controller agar tidak terjadi memory leak.

Tampilan UI (widget)

return Scaffold(
  appBar: AppBar(title: const Text("User Registration")),
  body: Padding(
    padding: const EdgeInsets.all(16.0),
    child: Form(
      key: _formKey,
      child: Column(
        children: [
          ...
        ],
      ),
    ),
  ),
);

Aplikasi ini menggunakan Scaffold, struktur layar standar dengan AppBar. Form dibungkus dengan _formKey. Isi Form berupa TextFormField dan tombol submit.

TextFormField untuk Nama

TextFormField(
                controller: _nameController,
                decoration: const InputDecoration(
                  labelText: "Nama",
                  prefixIcon: Icon(Icons.person),
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return "Nama tidak boleh kosong";
                  } else if (RegExp(r'[0-9]').hasMatch(value)) {
                    return "Format nama tidak valid";
                  }
                  return null;
                },
              ),
              const SizedBox(height: 16),

Textfield membuat validasi tidak boleh kosong dan tidak boleh angka

TextFormField untuk Email

TextFormField(
  controller: _emailController,
  decoration: const InputDecoration(
    labelText: "Email",
    prefixIcon: Icon(Icons.email),
  ),
  validator: (value) {
    if (value == null || value.isEmpty) {
      return "Email tidak boleh kosong";
    } else if (!value.contains('@')) {
      return "Format email tidak valid";
    }
    return null;
  },
),

Text field sudah memenuhi syarat Validasi yaitu tidak boleh kosong dan harus ada karakter @.

TextFormField untuk Password

TextFormField(
  controller: _passwordController,
  decoration: const InputDecoration(
    labelText: "Password",
    prefixIcon: Icon(Icons.lock),
  ),
  obscureText: true,
  validator: (value) {
    if (value == null || value.isEmpty) {
      return "Password tidak boleh kosong";
    }
    return null;
  },
),

Input password disembunyikan dengan obscureText: true dan validasi tidak boleh kosong.

TextFormField untuk Confirm Password

TextFormField(
  controller: _confirmPasswordController,
  decoration: const InputDecoration(
    labelText: "Confirm Password",
    prefixIcon: Icon(Icons.lock_outline),
  ),
  obscureText: true,
  validator: (value) {
    if (value == null || value.isEmpty) {
      return "Konfirmasi password tidak boleh kosong";
    } else if (value != _passwordController.text) {
      return "Password tidak sama";
    }
    return null;
  },
),

Field ini juga menggunakan obscureText: true dan validasi tidak boleh kosong. Bedanya, field ini isinya harus sama dengan field password

Tombol Submit

ElevatedButton(
  onPressed: _submitForm,
  child: const Text("Register"),
),

Jika ditekan akan memanggil _submitForm().

Hasilnya akan seperti berikut

Jika dijalankan benar akan seperti berikut:

Jika salah akan seperti berikut:

Link Github: https://github.com/farhnsaptr/hai

Leave a Comment

Your email address will not be published. Required fields are marked *