Blog

Input Widgets dan Basic Form

Praktikum kali ini bertujuan untuk membuat beberapa input widgets dan mengontrol inputan dari user. Sebelum melakukan praktikum ada beberapa hal yang perlu kita ketahui, langsung saja masuk ke pembahasan kita pada praktikum kali ini.

Basic Form

Basic Form merupakan widget yang berfungsi sebagai inputan nilai seperti TextField, TextFormField, CheckBox, dan sebagainya. Basic form kita gunakan untuk validasi dan mengelola inputan dari berbagai field.

Form akan memberikan tampilan inputan kemudian inputan akan diperiksa apakah sudah sesuai dengan aturan yang kita tetapkan, selanjutnya data inputan akan diambil nilainya setelah proses pengecekan selesai dilakukan.

Text Field

TextField adalah widget yang digunakan untuk memasukan text oleh pengguna, widget ini biasa digunakan untuk membuat form inputan seperti form login, pencarian, dan sebagainya . Fitur TextField ini dapat menerima input dari keyboard, memiliki properti lengkap (stle, decoration, dan jenis inputan), dan 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. Terdapat beberapa fitur TextFormField diantaranya :

  1. Menerima input teks dari keyboard.
  2. Memiliki properti validator yang berfungsi untuk memeriksa apakah input sudah sesuai
    dengan aturan yang ditentukan.
  3. Menampilkan pesan error secara otomatis di bawah field jika validasi gagal.
  4. 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. Kemudian, apa itu FormState? FormState adalah kelas yang mengelola status dari From, seperti status validasi setiap form inputan pada (TextFormField).

Menggunakan GlobalKey pada widget form, maka kita dapat memanggil metode seperti validate() atau save() dari luar widget, biasanya dari onPressed pada ElevatedButton.

Validate()

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

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

SetState()

setState merupakan sebuah method yang ada pada flutter, method ini digunakan untuk
rebuild ulang state yang ada pada StatefulWidget atau secara sederhana method ini
digunakan untuk memberitahu flutter bahwa state yang ada pada StatefulWidget telah
berubah dan widget tersebut perlu untuk rebuild.

Misalkan kita memiliki variable name yang mempunyai value “sakura” yang akan diubah
menjad “sakura sayonara” didalam StatefulWidget maka tampilan teks tersebut tidak
akan berubah sebelum memanggil method setState().

Const

Kata kunci const pada widget Flutter digunakan untuk membuat widget menjadi konstanta
pada saat kompilasi (compile-time). Penggunaan const dapat meningkatkan peforma karena widget const akan dibuat sekali dan akan disimpan pada memori sehingga dapat digunakan Kembali tanpa build ulang. Jika tidak menggunakan const maka widget akan di build() berkali-kali.

Setelah memahami materi di atas mari kita eksekusi dan implementasikan dengan mengikuti langkah berikut :

A. Basic Form TextField

  1. Buat file dart baru dengan nama form-textfield di dalam folder lib
  2. Buat dan pahami komen pada kodingan seperti berikut
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());   //entry poin apk flutter

class MyApp extends StatelessWidget { //di sini kita make statelees dlu karna tampilannya statis
  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 {  //nah di myform ini kita make stateful karena tampilannya bisa berubah 
  const MyForm({super.key});

  @override
  State<MyForm> createState() => _MyFormState();  //ini tuh kita bikin state yang menghubungkan ke _MyFormState
  //jadi Validasi untuk MyForm  di atur di dalam _MyFormState
}

//nah kita bikin kelas privat _MyformState 
//kita bikin privat biar ga bisa semabarang diakses dari luar
//Kita extend juga sama State<MyForm> biar semua daata yang bisa berubah ditaruh di sini 
//Kalau data di _MyFormState berubah, otomatis UI MyForm akan di-rebuild
class _MyFormState extends State<MyForm> {
  final TextEditingController _controller = TextEditingController();
  //TextEditingController dipakai untuk mengontrol dan membaca isi TextField.
  //kita bisa ambil textnya dengan _controller.text
  //kita pake final karena controller itu ga berubah referensinya tapi isinya bisa berubah
  String _nama = ""; //variabel ini untuk menyimpan hasil input setelah tombol ditekan

  @override
  void dispose() {    //dipanggil otomatis ketika kita pindah halaman
    _controller.dispose(); //dipanggil untuk membersihkan resource 
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(20.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('Masukan nama anda:'),
          const SizedBox(height: 10),
          TextField(
            controller: _controller,    //nah di sini kita memanggil _controller
            keyboardType: TextInputType.text,
            decoration: const InputDecoration(   //decoration text
              labelText: 'Nama Lengkap',
              hintText: 'Contoh Sherly',
              border: OutlineInputBorder(),
              prefixIcon: Icon(Icons.person),
            ),
            onChanged: (text) {  //fungsi onChanged ini dipanggil setiap TextField berubah
              print('Sedang mengetik teks: $text'); 
              //$text di sini ialah parameter fungsi anonim agar flutter otomatis mengirimkan nilai teks terbaru ke parameter tiap user ngetik
            },
          ),
          const SizedBox(height: 20),
          ElevatedButton(
            onPressed: () {
              setState(() {
                _nama = _controller.text;
              });

            },
            style: ElevatedButton.styleFrom(
              backgroundColor: Colors.amber,
              foregroundColor: Colors.black,
            ),
            child: const Text('Tampilkan nama'),
          ),
          const SizedBox(height: 20),
          Text(
            "Halo, $_nama",
            style: const TextStyle(fontSize: 18),
          ),
        ],
      ),
    );
  }
}

B. form-textformfield

  1. Bikin file form-textformfield.dart pada lib
  2. Buat dan pahami komen pada kodingan 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 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>();  
//GlobalKey ini kita gunakan agar kita bisa memanggil validate()ke semua TextFOrmField dalam form.
//kalo pada TextField sebelumnya tidak ada form & globalkey jadi validasinya manual 

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

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

  void _submitForm() {  //ini tuh untuk validasi otomatis
    if (_formKey.currentState!.validate()) {
      String name = _nameController.text;
      String email = _emailController.text;
//Di TextFormField, validasi bisa dijalankan sekaligus untuk semua //input dengan formKey.currentState!.validate().
//Setiap field akan menjalankan fungsi validator-nya masing-masing.
//Kalau semua valid → lanjut ambil data (_nameController.text).
//Sedangkan kalau TextField → validasi dilakukan manual per field, tidak bisa pakai validate()

      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Validasi berhasil: $name, $email')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            const SizedBox(height: 10),
            TextFormField(  //inputnya make textformfield di bagian ini
              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'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

C. Tugas

Setelah melakukan praktikum membuat basic form menggunakan widget TextField dan
TextFormField, silahkan kerjakan Tugas berikut ini.

Instruksi :

  • Buatlah aplikasi kalkulator yang dapat menjalankan operasi kabataku
  • Gunakan 2 buah widget inputan (TextField atau TextFormField) untuk menerima nilai
    inputan.
  • Gunakan ElevatedButton untuk mengeksekusi operasi kabataku
  • Gunakan widget Text untuk menampilkan hasil operasi

Jawaban :

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("Kalkulator APP"),
        ),
        body: const MyCalcu(),
      ),
    );
  }
}

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

  @override
  State<MyCalcu> createState() => _MyCalcu();
}

class Kalkulator {
  double tambah(double a, double b) => a + b;
  double kurang(double a, double b) => a - b;
  double kali(double a, double b) => a * b;
  double bagi(double a, double b) {
    if (b == 0) throw ArgumentError('Division by zero');
    return a / b;
  }
}

class _MyCalcu extends State<MyCalcu> {
  final _formKey = GlobalKey<FormState>();
  final _a = TextEditingController();
  final _b = TextEditingController();
  final kalk = Kalkulator();

  String _result = '';

  @override
  void dispose() {
    _a.dispose();
    _b.dispose();
    super.dispose();
  }

  void _calculate(String op) {
    if (!_formKey.currentState!.validate()) return;

    final a = double.tryParse(_a.text);
    final b = double.tryParse(_b.text);

    if (a == null || b == null) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Masukan angka yang valid')),
      );
      return;
    }

    double hasil;
    try {
      switch (op) {
        case '+':
          hasil = kalk.tambah(a, b);
          break;
        case '-':
          hasil = kalk.kurang(a, b);
          break;
        case 'x':
          hasil = kalk.kali(a, b);
          break;
        case '÷':
          hasil = kalk.bagi(a, b);
          break;
        default:
          return;
      }

      setState(() {
        _result = hasil.toString();
      });
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error: ${e.toString()}')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            const SizedBox(height: 10),
            TextFormField(
              controller: _a,
              decoration: const InputDecoration(
                labelText: "Inputkan angka 1",
                border: OutlineInputBorder(),
              ),
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Masukkan angka';
                }
                if (double.tryParse(value) == null) {
                  return 'Bukan angka';
                }
                return null;
              },
            ),
            const SizedBox(height: 10),
            TextFormField(
              controller: _b,
              decoration: const InputDecoration(
                labelText: "Inputkan angka 2",
                border: OutlineInputBorder(),
              ),
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Masukkan angka';
                }
                if (double.tryParse(value) == null) {
                  return 'Bukan angka';
                }
                return null;
              },
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton(
                  onPressed: () => _calculate('+'),
                  child: const Text('+'),
                ),
                ElevatedButton(
                  onPressed: () => _calculate('-'),
                  child: const Text('-'),
                ),
                ElevatedButton(
                  onPressed: () => _calculate('x'),
                  child: const Text('x'),
                ),
                ElevatedButton(
                  onPressed: () => _calculate('÷'),
                  child: const Text('÷'),
                ),
              ],
            ),
            const SizedBox(height: 20),
            Text(
              'Hasil: $_result',
              style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            )
          ],
        ),
      ),
    );
  }
}

Leave a Reply

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