Laprak Aplikasi Mobile – Input Widgets dan Basic Forms

Tujuan

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

  1. Membuat beberapa input widgets
  2. Membuat dan mengontrol inputan dari user
Teori
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 Text Field:

  • 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.
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.

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.

Dengan menggunakan kata kunci const maka flutter akan membangun / compile widget-widget tersebut sekali. Jika method build() dipanggil Kembali karena ada perubahan state pada widget lain maka widget yang sudah menggunakan kata kunci const tidak akan di rebuild sehingga proses reload lebih cepat dan menghemat sumber daya. Secara default flutter akan secara otomatis menjadikan widget menggunakan kata kunci const Ketika kode program disimpan.


Langkah-Langkah
Basic Form TextField
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 createState() => _MyFormState();
}

class _MyFormState extends State {
  
  final TextEditingController _teksEditingController = TextEditingController();

  String inputText = '';

  @override
  void dispose() {
    _teksEditingController.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 :',
            style: TextStyle(fontSize: 16),
          ),
          const SizedBox(height: 10),
          
          TextField(
            controller: _teksEditingController,
            keyboardType: TextInputType.text,
            onChanged: (text) {
              print('Sedang mengetik teks : $text');
            },
            decoration: const InputDecoration(
              labelText: 'Nama Lengkap',
              hintText: 'Nama Lengkap',
              border: OutlineInputBorder(),
              prefixIcon: Icon(Icons.person),
            ),
          ),
          const SizedBox(height: 20),
          ElevatedButton(
            onPressed: () {
              String inputText = _teksEditingController.text;
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('Nama anda adalah $inputText!')),
              );
              setState(() {
                inputText = _teksEditingController.text;
              });
            },
            style: ElevatedButton.styleFrom(
              backgroundColor: Colors.amber,
              foregroundColor: Colors.black,
            ),
            child: const Text('Tampilkan Nama'),
          ),
          const SizedBox(height: 20),

            Text(
                'Nama Anda: ${_teksEditingController.text}',
                style: const TextStyle(fontSize: 20)
            ),
        ],
      )
    );
  }
}
Basic Form TextFormField
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 createState() => _MyFormTextState();
}

class _MyFormTextState extends State {
  final _formKey = GlobalKey();
  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(
          mainAxisAlignment: MainAxisAlignment.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'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
Tugas

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

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'),
        ),
        body: const CalculatorForm(),
      ),
    );
  }
}

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

  @override
  State createState() => _CalculatorFormState();
}

class _CalculatorFormState extends State {
  final TextEditingController _controller1 = TextEditingController();
  final TextEditingController _controller2 = TextEditingController();

  String _hasil = '0';

  void _hitung(String operasi) {
    final double? angka1 = double.tryParse(_controller1.text);
    final double? angka2 = double.tryParse(_controller2.text);

    if (angka1 == null || angka2 == null) {
      setState(() {
        _hasil = 'Input tidak valid!';
      });
      return;
    }

    double? hasilPerhitungan;
    switch (operasi) {
      case 'kali':
        hasilPerhitungan = angka1 * angka2;
        break;
      case 'bagi':
        if (angka2 == 0) {
          hasilPerhitungan = double.infinity;
        } else {
          hasilPerhitungan = angka1 / angka2;
        }
        break;
      case 'tambah':
        hasilPerhitungan = angka1 + angka2;
        break;
      case 'kurang':
        hasilPerhitungan = angka1 - angka2;
        break;
    }

    setState(() {
      if (hasilPerhitungan != null) {
        if (hasilPerhitungan.isInfinite) {
          _hasil = 'Tidak terhingga';
        } else {
          _hasil = hasilPerhitungan.toString();
        }
      }
    });
  }
  
  @override
  void dispose() {
    _controller1.dispose();
    _controller2.dispose();
    super.dispose();
  }


  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(20.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          TextField(
            controller: _controller1,
            keyboardType: TextInputType.number,
            decoration: const InputDecoration(
              labelText: 'Angka Pertama',
              border: OutlineInputBorder(),
            ),
          ),
          const SizedBox(height: 20),

          TextField(
            controller: _controller2,
            keyboardType: TextInputType.number,
            decoration: const InputDecoration(
              labelText: 'Angka Kedua',
              border: OutlineInputBorder(),
            ),
          ),
          const SizedBox(height: 20),
          
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              ElevatedButton(onPressed: () => _hitung('tambah'), child: const Text('+')),
              ElevatedButton(onPressed: () => _hitung('kurang'), child: const Text('-')),
              ElevatedButton(onPressed: () => _hitung('kali'), child: const Text('x')),
              ElevatedButton(onPressed: () => _hitung('bagi'), child: const Text('/')),
            ],
          ),
          const SizedBox(height: 30),
          
          const Text(
            'Hasil:',
            style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
          ),
          Text(
            _hasil,
            style: const TextStyle(fontSize: 24, color: Colors.blueAccent),
          ),
        ],
      ),
    );
  }
}