๐Ÿ“ฑ

Laporan Praktikum Aplikasi Mobile

Pengelolaan Input Widgets, Form Validation & State Management
Studi Kasus: Basic Form & Kalkulator Kabataku (Flutter / Dart)

Praktikum Aplikasi Mobile

Disusun Oleh
Ahmad Zahran
2411532004
Dosen Pengampu
Nurfiah, S.ST. M.Kom
Universitas Andalas ยท 2026

I. Pendahuluan

Pengembangan aplikasi mobile berbasis antarmuka grafis (GUI) modern menuntut kemampuan sistem dalam menangani interaksi pengguna secara interaktif dan real-time[cite: 2]. Dalam kerangka kerja Flutter dan bahasa pemrograman Dart, interaksi utama antara pengguna dengan aplikasi dikelola melalui komponen Input Widgets dan Basic Form[cite: 2]. Komponen-komponen ini berfungsi sebagai jembatan untuk menerima entri data teks dari keyboard perangkat, menangkap aksi event, serta melakukan pemrosesan data internal[cite: 2].

Secara teknis, pengembangan form pada Flutter tidak hanya terbatas pada penampilan entri teks (TextField), tetapi juga mencakup pengelolaan status internal (state management), validasi data input (FormState dan validator), serta manajemen alokasi memori (TextEditingController dan dispose)[cite: 2]. Memahami perbedaan antara Uncontrolled/Basic Input menggunakan TextField dan Validated Form Input menggunakan TextFormField merupakan aspek fundamental dalam arsitektur antarmuka Flutter[cite: 2].

Hubungan antara materi praktikum ini dengan pemrograman Dart/Flutter terletak pada bagaimana paradigma Widget Tree dan Stateful Widget bekerja[cite: 2]. Ketika pengguna memasukkan teks atau menekan tombol, logika Dart mengolah data input, mengeksekusi operasi matematika atau validasi, lalu memicu metode setState() untuk mengarahkan Flutter dalam memperbarui tampilan UI secara dinamis[cite: 2].

II. Tujuan Praktikum

1

Memahami konsep dasar Basic Form dan Input Widgets pada Flutter[cite: 2].

2

Mampu membuat dan mengontrol input teks pengguna menggunakan TextField dan TextFormField[cite: 2].

3

Memahami penggunaan TextEditingController untuk mengambil dan membersihkan data inputan keyboard[cite: 2].

4

Memahami fungsi dan penggunaan GlobalKey<FormState> serta metode validate() dalam mengelola validasi form secara kolektif[cite: 2].

5

Memahami fungsi penggunaan kata kunci const dan metode setState() dalam proses pengoperasian serta rebuild widget[cite: 2].

6

Mampu mengimplementasikan logika pemrosesan input dan operasi kalkulasi matematika (Kabataku) melalui aplikasi Flutter[cite: 2].

III. Langkah Kerja

3.1 Basic Form TextField (Membuat Tampilan Dasar)

Langkah yang dilakukan: Membuat file Dart baru bernama form-textfield.dart di dalam folder lib[cite: 2]. Pada langkah awal ini, dibangun struktur tampilan antarmuka Basic Form sederhana yang terdiri dari Scaffold, AppBar, Column, TextField, SizedBox, dan ElevatedButton[cite: 2].

Kode Program: form-textfield.dart
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'), ), ], ), ); } }

Penjelasan Kode:

  • import 'package:flutter/material.dart';: Mengimpor pustaka Material Design Flutter yang menyediakan komponen GUI standar[cite: 2].
  • void main() => runApp(const MyApp());: Fungsi utama (entry point) yang menjalankan aplikasi[cite: 2].
  • MaterialApp & Scaffold: Mengatur tema, navigasi, dan tata letak dasar aplikasi[cite: 2].
  • TextField: Menampilkan bidang input teks dengan hiasan label, hint, border, dan ikon[cite: 2].
  • ElevatedButton: Tombol aksi dengan kustomisasi warna latar belakang[cite: 2].

Hasil Praktikum: Aplikasi menampilkan halaman bertulisan judul "Basic Form" pada AppBar, label perintah, satu kotak input TextField berikon person, dan tombol "Tampilkan nama"[cite: 2].

Analisis: Tahap ini membentuk UI statis[cite: 2]. Namun, teks yang diketikkan pengguna belum dapat ditangkap oleh aplikasi karena belum dihubungkan dengan pengontrol teks (controller)[cite: 2].

3.2 Menambahkan Controller dan Dispose pada State
Penambahan TextEditingController & dispose()
TextEditingController _textEditingController = TextEditingController(); @override void dispose() { _textEditingController.dispose(); super.dispose(); }

Penjelasan Kode: Deklarasi TextEditingController bertanggung jawab membaca, menulis, dan memantau perubahan teks[cite: 2]. Metode dispose() membebaskan memori yang digunakan controller ketika widget dihapus guna mencegah kebocoran memori (memory leak)[cite: 2].

Analisis: Penerapan dispose() merupakan best practice dalam pemrograman berorientasi objek dan aplikasi mobile[cite: 2].

3.3 Menhubungkan Controller dan Listener pada TextField
Penggunaan Properti controller dan onChanged
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'); }, ),

Analisis: Properti controller membuat hubungan dua arah (two-way binding) antara UI dan variabel Dart[cite: 2]. Callback onChanged berguna untuk merespons input secara real-time ke log konsol[cite: 2].

3.4 Menambahkan Aksi pada Button untuk Menampilkan SnackBar
Menampilkan Feedback via SnackBar
onPressed: () { String inputText = _textEditingController.text; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Nama anda adalah $inputText')), ); },

Hasil & Analisis: Ketika pengguna menekan tombol, SnackBar akan muncul di bagian bawah layar menampilkan teks masukan[cite: 2]. Pembacaan _textEditingController.text berhasil memindahkan data input UI menuju feedback visual[cite: 2].

3.5 Basic Form TextFormField (Membuat Form Tervalidasi)

Langkah yang dilakukan: Membuat file baru bernama form-textformfield.dart[cite: 2]. Langkah ini menyusun form tervalidasi menggunakan widget TextFormField untuk dua field (Nama dan Email) serta satu tombol submit[cite: 2].

Kode Program: form-textformfield.dart
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( 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'), ), ), ], ), ), ); } }

Analisis Validasi Form: Integrasi GlobalKey<FormState> memungkinkan validasi kolektif diproses secara terpusat melalui panggilan metode _formKey.currentState!.validate()[cite: 2].

IV. Tugas Praktikum: Kalkulator Kabataku

4.1 Deskripsi & Algoritma Tugas

Tugas ini meminta mahasiswa membuat aplikasi Kalkulator Kabataku (Kali, Bagi, Tambah, Kurang) berbasis Flutter[cite: 2]. Aplikasi menerima dua nilai numerik dari pengguna melalui komponen input tervalidasi, memproses operasi aritmatika, serta menampilkan hasil perhitungan pada layar[cite: 2].

Algoritma Kerja:
  1. Pengguna memasukkan Angka Pertama dan Angka Kedua pada TextFormField[cite: 2].
  2. Pengguna menekan salah satu tombol operasi (+, -, x, รท)[cite: 2].
  3. Sistem memicu fungsi _hitung(operator) dan mengeksekusi validasi form[cite: 2].
  4. Jika valid, data dikonversi ke tipe double menggunakan double.parse()[cite: 2].
  5. Pada operasi pembagian (รท), dilakukan Pengecekan Khusus jika angka kedua bernilai 0[cite: 2].
  6. Sistem memanggil setState() untuk memperbarui tampilan variabel _hasil[cite: 2].
4.2 Kode Program: kalkulator_kabataku.dart
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( title: 'Kalkulator Kabataku', debugShowCheckedModeBanner: false, theme: ThemeData(primarySwatch: Colors.blue), home: const KalkulatorPage(), ); } } class KalkulatorPage extends StatefulWidget { const KalkulatorPage({super.key}); @override State<KalkulatorPage> createState() => _KalkulatorPageState(); } class _KalkulatorPageState extends State<KalkulatorPage> { final _formKey = GlobalKey<FormState>(); final TextEditingController _angka1Controller = TextEditingController(); final TextEditingController _angka2Controller = TextEditingController(); String _hasil = 'Hasil akan muncul di sini'; void _hitung(String operator) { if (!_formKey.currentState!.validate()) { return; } double angka1 = double.parse(_angka1Controller.text); double angka2 = double.parse(_angka2Controller.text); double hasil = 0; setState(() { switch (operator) { case '+': hasil = angka1 + angka2; _hasil = 'Hasil: ${angka1.toStringAsFixed(2)} + ${angka2.toStringAsFixed(2)} = ${hasil.toStringAsFixed(2)}'; break; case '-': hasil = angka1 - angka2; _hasil = 'Hasil: ${angka1.toStringAsFixed(2)} - ${angka2.toStringAsFixed(2)} = ${hasil.toStringAsFixed(2)}'; break; case 'x': hasil = angka1 * angka2; _hasil = 'Hasil: ${angka1.toStringAsFixed(2)} x ${angka2.toStringAsFixed(2)} = ${hasil.toStringAsFixed(2)}'; break; case 'รท': if (angka2 == 0) { _hasil = 'Error: Tidak bisa membagi dengan nol'; } else { hasil = angka1 / angka2; _hasil = 'Hasil: ${angka1.toStringAsFixed(2)} รท ${angka2.toStringAsFixed(2)} = ${hasil.toStringAsFixed(2)}'; } break; } }); } @override void dispose() { _angka1Controller.dispose(); _angka2Controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Kalkulator Kabataku'), centerTitle: true, ), body: Padding( padding: const EdgeInsets.all(20.0), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: 10), TextFormField( controller: _angka1Controller, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration( labelText: 'Angka Pertama', border: OutlineInputBorder(), prefixIcon: Icon(Icons.numbers), ), validator: (value) { if (value == null || value.isEmpty) { return 'Angka pertama tidak boleh kosong'; } if (double.tryParse(value) == null) { return 'Masukkan angka yang valid'; } return null; }, ), const SizedBox(height: 16), TextFormField( controller: _angka2Controller, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration( labelText: 'Angka Kedua', border: OutlineInputBorder(), prefixIcon: Icon(Icons.numbers), ), validator: (value) { if (value == null || value.isEmpty) { return 'Angka kedua tidak boleh kosong'; } if (double.tryParse(value) == null) { return 'Masukkan angka yang valid'; } return null; }, ), const SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ElevatedButton( onPressed: () => _hitung('+'), style: ElevatedButton.styleFrom( backgroundColor: Colors.green, foregroundColor: Colors.white, ), child: const Text('+', style: TextStyle(fontSize: 20)), ), ElevatedButton( onPressed: () => _hitung('-'), style: ElevatedButton.styleFrom( backgroundColor: Colors.orange, foregroundColor: Colors.white, ), child: const Text('-', style: TextStyle(fontSize: 20)), ), ElevatedButton( onPressed: () => _hitung('x'), style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, foregroundColor: Colors.white, ), child: const Text('x', style: TextStyle(fontSize: 20)), ), ElevatedButton( onPressed: () => _hitung('รท'), style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, ), child: const Text('รท', style: TextStyle(fontSize: 20)), ), ], ), const SizedBox(height: 32), Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey), ), child: Text( _hasil, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w500, ), textAlign: TextAlign.center, ), ), ], ), ), ), ); } }
4.3 Pengujian Program & Hasil Evaluasi
Skenario Pengujian:
  • Skenario 1 (Penjumlahan): Input 15.5 dan 4.5, tombol +. Output: Hasil: 15.50 + 4.50 = 20.00[cite: 2].
  • Skenario 2 (Pembagian Nol): Input 10 dan 0, tombol รท. Output: Error: Tidak bisa membagi dengan nol[cite: 2].
  • Skenario 3 (Input Kosong): Field kosong, tombol x. Output: Teks peringatan error berwarna merah muncul[cite: 2].
  • Skenario 4 (Non-Numerik): Input "12.a" dan 3, tombol -. Output: Pesan error Masukkan angka yang valid[cite: 2].

V. Kesimpulan

Hasil & Kesimpulan Praktikum
  • Pemahaman mengenai perbedaan penggunaan TextField untuk entri data sederhana tanpa validasi terintegrasi dan TextFormField untuk form yang membutuhkan verifikasi aturan data secara terpusat telah berhasil dikuasai[cite: 2].
  • Penggunaan objek TextEditingController terbukti sangat mendasar dalam menghubungkan nilai masukan antarmuka pengguna dengan logika internal bahasa pemrograman Dart[cite: 2].
  • Penerapan GlobalKey<FormState> dan fungsi validate() mempermudah eksekusi validasi secara kolektif[cite: 2].
  • Pemahaman terhadap manajemen memori aplikasi diperoleh melalui penerapan metode dispose() untuk membersihkan controller ketika widget sudah tidak digunakan[cite: 2].
  • Aplikasi Kalkulator Kabataku terbukti mampu melakukan operasi penambahan, pengurangan, perkalian, dan pembagian secara akurat, lengkap dengan penanganan error input kosong, validasi numerik, serta pencegahan pembagian dengan angka nol[cite: 2].
๐Ÿ™
Source Code Repository
Lihat seluruh kode program Aplikasi Mobile di GitHub
AhmadZahran1011 / AppMobile-AhmadZahran