Flutter merupakan framework yang digunakan untuk membangun aplikasi dengan menggunakan bahasa pemrograman Dart. Dalam Flutter, tampilan aplikasi dibangun menggunakan konsep widget.
StatelessWidget merupakan widget yang bersifat statis. Widget ini tidak memiliki state internal yang berubah setelah widget dirender. Contohnya adalah teks, ikon, maupun tampilan statis lainnya.
StatefulWidget merupakan widget yang dapat mengalami perubahan state sebagai respons terhadap interaksi pengguna atau perubahan data. StatefulWidget memiliki objek State yang digunakan untuk menyimpan data yang dapat berubah.
setState() digunakan untuk memberi tahu Flutter bahwa terdapat perubahan state sehingga tampilan dapat dirender ulang. Pada praktikum ini setState() digunakan untuk mengubah tampilan saldo pada kartu ketika ikon mata ditekan.
Row digunakan untuk menyusun widget secara horizontal, sedangkan Column digunakan untuk menyusun widget secara vertikal. Kedua widget tersebut digunakan untuk mengatur struktur tampilan aplikasi.
Container dapat digunakan untuk memberikan padding, margin, warna, dan dekorasi pada sebuah widget. Sementara itu, ListTile merupakan widget Material Design yang digunakan untuk membuat baris daftar dengan elemen leading, title, subtitle, dan trailing.
SingleChildScrollView digunakan agar konten yang memiliki ukuran lebih panjang daripada layar dapat digulir. Pada Praktikum 2, widget ini digunakan untuk membungkus seluruh isi dashboard.
flutter create praktikum_1
Setelah project berhasil dibuat, project dapat dibuka menggunakan Visual Studio Code atau Android Studio.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
// 1. WIDGET UTAMA
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Expense Tracker',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const DashboardScreen(),
);
}
}
MaterialApp digunakan sebagai dasar aplikasi Flutter dan Scaffold digunakan untuk membuat struktur halaman.
class DashboardScreen extends StatelessWidget {
const DashboardScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Praktikum 1: Widgets'),
backgroundColor: Colors.blue,
),
body: const Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
GreetingWidget(),
SizedBox(height: 20),
BalanceCardWidget(),
],
),
),
);
}
}
Column digunakan untuk menyusun GreetingWidget dan BalanceCardWidget secara vertikal.
class GreetingWidget extends StatelessWidget {
const GreetingWidget({super.key});
@override
Widget build(BuildContext context) {
return const Row(
children: [
CircleAvatar(
radius: 24,
backgroundColor: Colors.blueAccent,
child: Icon(
Icons.person,
size: 30,
color: Colors.white,
),
),
SizedBox(width: 12),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
'Halo, Budi',
style: TextStyle(
fontSize: 20,
fontWeight:
FontWeight.bold,
),
),
Text(
'Selamat datang kembali!',
style: TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
],
),
],
);
}
}
Row digunakan untuk menempatkan avatar dan teks secara horizontal, sedangkan Column digunakan untuk menempatkan dua teks sapaan secara vertikal.
class BalanceCardWidget extends StatefulWidget {
const BalanceCardWidget({super.key});
@override
State createState() =>
_BalanceCardWidgetState();
}
class _BalanceCardWidgetState
extends State {
bool _isBalanceVisible = true;
void _toggleVisibility() {
setState(() {
_isBalanceVisible =
!_isBalanceVisible;
});
}
@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(16),
),
color: Colors.blueAccent,
child: Padding(
padding:
const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
const Text(
'Saldo Utama',
style: TextStyle(
fontSize: 16,
color:
Colors.white70,
),
),
IconButton(
icon: Icon(
_isBalanceVisible
? Icons.visibility
: Icons.visibility_off,
color: Colors.white,
),
onPressed:
_toggleVisibility,
),
],
),
const SizedBox(height: 8),
Text(
_isBalanceVisible
? 'Rp 5.000.000'
: 'Rp *********',
style: const TextStyle(
fontSize: 28,
fontWeight:
FontWeight.bold,
color: Colors.white,
),
),
],
),
),
);
}
}
Variabel _isBalanceVisible digunakan untuk menyimpan status apakah saldo sedang ditampilkan atau disembunyikan.
Fungsi _toggleVisibility() menggunakan setState() untuk mengubah nilai state tersebut sehingga UI dapat diperbarui.
flutter run -d chrome -t lib/main.dart
Setelah dijalankan, aplikasi menampilkan sapaan pengguna dan kartu saldo. Ikon mata pada kartu saldo dapat ditekan untuk mengubah tampilan saldo.
class DashboardScreen extends StatelessWidget {
const DashboardScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title:
const Text(
'Praktikum 2: Layouting'),
backgroundColor:
Colors.blue,
),
body:
const SingleChildScrollView(
child: Padding(
padding:
EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
GreetingWidget(),
SizedBox(height: 20),
BalanceCardWidget(),
SizedBox(height: 20),
ActionButtonsWidget(),
SizedBox(height: 20),
RecentTransactionsWidget(),
],
),
),
),
);
}
}
SingleChildScrollView digunakan agar seluruh isi dashboard dapat digulir ketika konten melebihi ukuran layar.
class ActionButtonsWidget
extends StatelessWidget {
const ActionButtonsWidget({
super.key,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: [
_buildActionButton(
Icons.arrow_downward,
'Pemasukan',
Colors.green,
),
_buildActionButton(
Icons.arrow_upward,
'Pengeluaran',
Colors.red,
),
_buildActionButton(
Icons.swap_horiz,
'Transfer',
Colors.blue,
),
],
);
}
Widget _buildActionButton(
IconData icon,
String label,
Color color,
) {
return Column(
children: [
Container(
padding:
const EdgeInsets.all(12),
decoration:
BoxDecoration(
color:
color.withOpacity(0.2),
borderRadius:
BorderRadius.circular(12),
),
child: Icon(
icon,
color: color,
size: 28,
),
),
const SizedBox(height: 8),
Text(
label,
style:
const TextStyle(
fontWeight:
FontWeight.w500,
),
),
],
);
}
}
Row digunakan untuk membuat tiga tombol tersusun secara horizontal. Nilai spaceEvenly digunakan untuk membagi ruang kosong secara merata.
class RecentTransactionsWidget
extends StatelessWidget {
const RecentTransactionsWidget({
super.key,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
const Text(
'Transaksi Terakhir',
style: TextStyle(
fontSize: 18,
fontWeight:
FontWeight.bold,
),
),
const SizedBox(height: 12),
Card(
elevation: 2,
shape:
RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(12),
),
child: Column(
children: [
ListTile(
leading:
const CircleAvatar(
backgroundColor:
Colors.redAccent,
child: Icon(
Icons.fastfood,
color:
Colors.white,
),
),
title:
const Text(
'Makan Siang'),
subtitle:
const Text(
'13 Sep 2026'),
trailing:
const Text(
'- Rp 50.000',
style: TextStyle(
color: Colors.red,
fontWeight:
FontWeight.bold,
),
),
),
const Divider(height: 1),
ListTile(
leading:
const CircleAvatar(
backgroundColor:
Colors.green,
child: Icon(
Icons.attach_money,
color:
Colors.white,
),
),
title:
const Text(
'Gaji Bulanan'),
subtitle:
const Text(
'01 Sep 2026'),
trailing:
const Text(
'+ Rp 5.000.000',
style: TextStyle(
color: Colors.green,
fontWeight:
FontWeight.bold,
),
),
),
const Divider(height: 1),
ListTile(
leading:
const CircleAvatar(
backgroundColor:
Colors.blueAccent,
child: Icon(
Icons.directions_car,
color:
Colors.white,
),
),
title:
const Text(
'Isi Bensin'),
subtitle:
const Text(
'10 Sep 2026'),
trailing:
const Text(
'- Rp 150.000',
style: TextStyle(
color: Colors.red,
fontWeight:
FontWeight.bold,
),
),
),
],
),
),
],
);
}
}
Column digunakan untuk menyusun daftar transaksi dari atas ke bawah. ListTile digunakan agar setiap transaksi memiliki struktur yang rapi.
Komponen yang digunakan adalah:
Seluruh komponen ditempatkan dalam Column dan dibungkus dengan SingleChildScrollView agar halaman dapat di-scroll.
flutter run -d chrome -t lib/main.dart
Hasil akhir berupa dashboard sederhana yang menampilkan sapaan pengguna, kartu saldo, tombol aksi, dan daftar transaksi.
color: Colors.teal,
Perubahan dilakukan pada property color milik Card pada BalanceCardWidget.
const Text(
'No. Rekening: 1234-5678',
style: TextStyle(
color: Colors.white70,
),
),
Dengan penambahan tersebut, kartu saldo menampilkan informasi saldo sekaligus nomor rekening pengguna.
Pengeluaran menggunakan warna merah.
trailing: const Text(
'- Rp 50.000',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
),
),
Pemasukan menggunakan warna hijau.
trailing: const Text(
'+ Rp 5.000.000',
style: TextStyle(
color: Colors.green,
fontWeight: FontWeight.bold,
),
),
ListTile(
leading:
const CircleAvatar(
backgroundColor:
Colors.redAccent,
child: Icon(
Icons.shopping_cart,
color: Colors.white,
),
),
title:
const Text(
'Belanja Bulanan'),
subtitle:
const Text(
'15 Sep 2026'),
trailing:
const Text(
'- Rp 300.000',
style: TextStyle(
color: Colors.red,
fontWeight:
FontWeight.bold,
),
),
),
const Divider(height: 1),
ListTile(
leading:
const CircleAvatar(
backgroundColor:
Colors.green,
child: Icon(
Icons.card_giftcard,
color: Colors.white,
),
),
title:
const Text(
'Bonus'),
subtitle:
const Text(
'16 Sep 2026'),
trailing:
const Text(
'+ Rp 500.000',
style: TextStyle(
color: Colors.green,
fontWeight:
FontWeight.bold,
),
),
),
Transaksi "Belanja Bulanan" digunakan sebagai contoh pengeluaran, sedangkan "Bonus" digunakan sebagai contoh pemasukan.
Berdasarkan praktikum yang telah dilakukan, pada Praktikum 1 berhasil diterapkan konsep dasar widget pada Flutter. GreetingWidget dibuat menggunakan StatelessWidget karena tampilannya bersifat statis, sedangkan BalanceCardWidget menggunakan StatefulWidget karena memiliki state yang dapat berubah.
Penggunaan setState() pada BalanceCardWidget memungkinkan tampilan saldo berubah ketika pengguna menekan ikon mata. Ketika state bernilai true, saldo ditampilkan sebagai Rp 5.000.000. Ketika state berubah, saldo ditampilkan dalam bentuk sensor.
Pada Praktikum 2, tampilan aplikasi dikembangkan menggunakan konsep layouting dan styling. Row digunakan untuk menyusun tombol aksi secara horizontal, sedangkan Column digunakan untuk menyusun daftar transaksi secara vertikal.
ListTile digunakan untuk menyusun informasi transaksi yang terdiri dari ikon, nama transaksi, tanggal, dan nominal. Penggunaan warna pada trailing membantu membedakan transaksi pengeluaran dan pemasukan.
SingleChildScrollView juga digunakan pada dashboard agar seluruh komponen tetap dapat ditampilkan dan halaman dapat digulir ketika isi dashboard melebihi ukuran layar.
Pada tugas praktikum, BalanceCardWidget berhasil dimodifikasi dengan mengganti warna kartu menjadi Colors.teal dan menambahkan informasi nomor rekening. Selain itu, dua transaksi fiktif berhasil ditambahkan ke dalam daftar transaksi pada Praktikum 2.
Dari praktikum yang telah dilakukan dapat disimpulkan bahwa Flutter menggunakan widget sebagai komponen utama dalam membangun antarmuka aplikasi.
Pada Praktikum 1, dipelajari perbedaan antara StatelessWidget dan StatefulWidget. StatelessWidget digunakan untuk tampilan yang bersifat statis, sedangkan StatefulWidget digunakan ketika terdapat data atau tampilan yang dapat berubah.
Penggunaan setState() memungkinkan perubahan state ditampilkan kembali pada UI. Konsep tersebut diterapkan pada kartu saldo yang dapat menampilkan maupun menyembunyikan nominal saldo.
Pada Praktikum 2, dipelajari penggunaan Row, Column, Container, ListTile, dan SingleChildScrollView untuk membangun tampilan dashboard yang lebih terstruktur.
Dengan menggabungkan kedua praktikum, dapat dipahami dasar pembuatan UI Flutter, pengelolaan state, penyusunan layout, serta pemberian styling pada aplikasi mobile sederhana.