Bab 3: Widget Dasar
Masuki dunia Flutter, di mana segalanya adalah widget! Bab ini mengungkap tiga kategori widget fundamental yang penting:
• Struktur dan navigasi • Menampilkan informasi • Menempatkan widget
Di akhir bab, kamu akan membangun sebuah aplikasi sosial bertema makanan bernama Yummy. Kamu akan menggunakan berbagai widget untuk membuat tiga tab yang berbeda: Category, Post, dan Restaurant.

Siap? Mari kita lihat starter project-nya.
Ayo Mulai
Mulailah dengan mengunduh aplikasi untuk bab ini dari https://github.com/kodecocodes/flta-materials.
Temukan folder projects lalu buka folder starter. Setelah itu, navigasikan ke pubspec.yaml dan jalankan Pub get untuk mengambil semua dependensi Flutter yang dibutuhkan.

Jalankan aplikasi dan kamu akan melihat sebuah app bar dan teks sederhana:

File lib/main.dart berisi kode awal. Buka untuk melihat isinya:
import 'package:flutter/material.dart';
void main() {
// 1
runApp(const Yummy());
}
class Yummy extends StatelessWidget {
// TODO: Setup default theme
// 2
const Yummy({super.key});
// TODO: Tambahkan changeTheme di sini
@override
Widget build(BuildContext context) {
const appTitle = 'Yummy';
// TODO: Setup default theme
//3
return MaterialApp(
title: appTitle,
//debugShowCheckedModeBanner: false, // Hapus komentar untuk menghilankan Debug banner
// TODO: Tambahkan theme
// TODO: Timpa Scaffold dengan widget Home
// 4
home: Scaffold(
appBar: AppBar(
// TODO: Tambahkan 52 action buttons
elevation: 4.0,
title: const Text(
appTitle,
style: TextStyle(fontSize: 24.0),
),
),
body: const Center(
child: Text(
'You Hungry?!',
style: TextStyle(fontSize: 30.0),
),
),
),
);
}
}
Luangkan waktu sejenak untuk memahami apa yang dilakukan kode berikut:
- Inisialisasi Widget: Setiap aplikasi Flutter selalu dimulai dari sebuah widget. Fungsi runApp() menginisialisasi aplikasi dengan menerima root widget, dalam hal ini berupa sebuah instance dari Yummy.
- Setiap widget wajib meng-override method
build(). - Widget Yummy dimulai dengan sebuah
MaterialAppuntuk memberikan tampilan dan nuansa Material Design. Untuk detail lebih lanjut, lihat https://material.io. - Scaffold mendefinisikan struktur tampilan aplikasi, yang berisi sebuah AppBar dan sebuah body.
Mengatur Tampilan Aplikasi
Flutter, karena bersifat cross-platform, mendukung Material Design dari Android dan juga design system Cupertino dari iOS.

Android menggunakan sistem Material Design yang bisa diimport dengan cara:
import 'package:flutter/material.dart';
Sementara iOS menggunakan Cupertino yang bisa diimport dengan:
import 'package:flutter/cupertino.dart';
Dalam buku ini kita akan belajar menggunakan sistem Material Design.
Berpindah dari Material ke Cupertino atau sebaliknya diluar pembahasan buku ini. Untuk mempelajari lebih jauh tentang apa yang ditawarkan dari sisi UI silahkan lihat:
• Material UI Components: https://flutter.dev/docs/development/ui/widgets/ material • Cupertino UI Components: https://flutter.dev/docs/development/ui/widgets/ cupertino
Sekarang mari kita lanjutkan dengan mengatur tema aplikasi.
Menentukan Theme Class
Mari percantik aplikasi dengan tema sendiri! Dengan Material 3, theme management dipermudah dengan fokus pada variasi warna.

Buka lib/constants.dart dan pelajari kode yang ada:
import 'package:flutter/material.dart';
enum ColorSelection {
// 1
deepPurple('Deep Purple', Colors.deepPurple),
purple('Purple', Colors.purple),
indigo('Indigo', Colors.indigo),
blue('Blue', Colors.blue),
teal('Teal', Colors.teal),
green('Green', Colors.green),
yellow('Yellow', Colors.yellow),
orange('Orange', Colors.orange),
deepOrange('Deep Orange', Colors.deepOrange),
pink('Pink', Colors.pink);
// 2
const ColorSelection(
this.label,
this.color,
);
final String label;
final Color color;
}
Nilai enum pada ColorSelection memungkinkan pengguna untuk mengubah tampilan aplikasi dengan cara:
- Mengubah warna tema (misalnya Deep Purple).
- Setiap pilihan akan memiliki label teks dan warnanya yang diwakili oleh objek
color.
Berikutnya, mari kita implementasi tema warna ke aplikasi.
Menggunakan Tema
Buka main.dart, import warna yang sudah dibuat:
import 'constants.dart';
Cari //TODO: Setup default theme dan timpe dengan kode berikut untuk menentukan tema awal dan warna utama, abaikan garis merah yang muncul:
ThemeMode themeMode = ThemeMode.light; // Manual theme toggle
ColorSelection colorSelected = ColorSelection.pink;
Selanjutnya, cari komentar //TODO: Tambahkan theme kemudian ganti dengan kode berikut untuk mengaplikasi tema tersebut:
themeMode: themeMode,
theme: ThemeData(
colorSchemeSeed: colorSelected.color,
useMaterial3: true,
brightness: Brightness.light,
),
darkTheme: ThemeData(
colorSchemeSeed: colorSelected.color,
useMaterial3: true,
brightness: Brightness.dark,
),
Potongan kode ini mengatur theme mode secara global. Kode tersebut mendefinisikan light theme dan dark theme dengan memanfaatkan warna yang sebelumnya sudah kamu tentukan, sehingga tampilan visual aplikasi tetap konsisten dan adaptif di seluruh bagian aplikasi.
Karena theme dapat berubah, kamu perlu menghapus const dari dua lokasi berikut:
runApp(const Yummy());
...
const Yummy({super.key});
Simpan perubahan dan lakukan hot restart untuk melihat tampilan yang baru.

Cari // Manual theme toggle lalu ubah light menjadi dark untuk melihat variasi temanya. Pastikan untuk melakukan hot restart.
Kedua tema tadi seharusnya akan terlihat:

Selanjutnya, user akan bisa mengganti tema dari light menjadi dark serta memilih sendiri warna yang dimau.
Mengubah Tema
Agar bisa mengubah tema di dalam aplikasi, kita perlu bekerja dengan state dengan mengubah widget Yummy menjadi StatefulWidget. Kabar baikny akita bisa melakukan hal tersebut lewat menu klik kanan secara otomatis.
Klik kanan pada nama kelas Yummy. Kemudian klik Show Context Actions dari menu yang tampil:

Kemudian pilih Convert to StatefulWidget.

Sekarang akan ada dua kelas.
class Yummy extends StatefulWidget {
...
@override
State<Yummy> createState() => _YummyState_();
}
class _YummyState extends State<Yummy> {
...
@override
Widget build(BuildContext context) {
...
}
Beberapa hal yang perlu kamu perhatikan dari kode di atas:
• Proses refactor mengubah Yummy dari StatelessWidget menjadi StatefulWidget. Selain itu, ditambahkan implementasi createState().
• Refactor tersebut juga membuat kelas state _YummyState. Kelas ini menyimpan data yang bersifat mutable dan dapat berubah sepanjang siklus hidup widget.
Menyenangkan bukan, ketika ada cara otomatis yang bisa menghemat waktu? Selanjutnya, kamu akan mengimplementasikan perubahan theme state.
Mengubah State
Di dalam _YummyState cari `// TODO: Tambahkan changeTheme di sini dan timpa dengan:
void changeThemeMode(bool useLightMode) {
setState(() {
// 1
themeMode = useLightMode
? ThemeMode.light //
: ThemeMode.dark;
});
}
void changeColor(int value) {
setState(() {
// 2
colorSelected = ColorSelection.values[value];
});
}
Begini cara kerjanya:
- Ubah mode
lightataudarkberdasarkan pilihan pengguna. - Ubah warna berdasarkan pilihan pengguna.
Memanggil fungsi ini akan mengubah tema atau mengubah warna aplikasi. Sekarang kita perlu membuat tombol khusus agar bisa dipakai untuk mengubah keduanya.
Membuat Tombol Untuk Mengubah Warna dan Tema
Sekarang kita akan membuat tombol untuk mengubah team dari light ke dark dan sebaliknya. Buat sebuah folder baru di dalam lib bernama components kemudian buat file baru bernama **theme_button.dart:
import 'package:flutter/material.dart';
class ThemeButton extends StatelessWidget {
// 1
const ThemeButton({
Key? key,
required this.changeThemeMode,
}) : super(key: key);
// 2
final Function changeThemeMode;
@override
Widget build(BuildContext context) {
// 3
final isBright = Theme.of(context).brightness == Brightness.light;
// 4
return IconButton(
icon: isBright
? const Icon(Icons.dark_mode_outlined) //
: const Icon(Icons.light_mode_outlined),// 5
onPressed: () => changeThemeMode(!isBright),
);
}
}
Mari kita pahami kode di atas:
- Widget
ThemeButtonakan meminta parameter berupa fungsi bernamachangeThemeMode. - Fungsi
changeThemeModeadalah sebuah callback function yang dikirim sebagai sebuah parameter untuk dieksekusi saat pengguna menekan tombol tersebut. Fungsi ini akan memberitahu parent widget-nya untuk mengubah tema saat diperlukan. - Varaibel
isBrightadalah Boolean yang memeriksa apakah tema saat ini adalahlight. - Widget
IconButtonakan menampilkan gambar ikonlightataudarksesuai nilai dariisBright. - Saat ditekan,
IconButtonakan mengubah tema dengan memanggil methodchangeThemeMode.
Membuat Tombol Warna
Di folder lib/components, buat sebuah file baru bernama color_button.dart lalu tambahkan kode berikut:
import 'package:flutter/material.dart';
import '../constants.dart';
class ColorButton extends StatelessWidget {
// 1
const ColorButton({
super.key,
required this.changeColor,
required this.colorSelected,
});
// 2
final void Function(int) changeColor;
final ColorSelection colorSelected;
@override
Widget build(BuildContext context) {
// 3
return PopupMenuButton(
icon: Icon(
Icons.opacity_outlined,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
// 4
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
// 5
itemBuilder: (context) {
// 6
return List.generate(
ColorSelection.values.length,
(index) {
final currentColor = ColorSelection.values[index];
// 7
return PopupMenuItem(
value: index,
enabled: currentColor != colorSelected,
child: Wrap(
children: [
Padding(
padding: const EdgeInsets.only(left: 10),
child: Icon(
Icons.opacity_outlined,
color: currentColor.color,
),
),
Padding(
padding: const EdgeInsets.only(left: 20),
child: Text(currentColor.label),
),
],
),
);
},
);
},
// 8
onSelected: changeColor,
);
}
}
Berikut penjelasan kode sebelumny:
- Menginisialisasi
ColorButtondengan callback dan warna yang diperlukan. - Properti
changeColormerupakan callback untuk menangani pemilihan warna, sedangkancolorSelectedadalah warna yang sedang dipilih. - Membuat sebuah tombol yang menampilkan sebuah menu.
- Menerapkan sudut membulat pada popup menu.
- Menghasilkan item-item menu.
- Membuat daftar opsi warna dari
ColorSelection. - Mengonfigurasi setiap item menu dengan ikon dan teks.
- Memanggil
changeColorsaat sebuah item dipilih.
Setelah tombol-tombol ini selesai dibuat, sekarang saatnya menambahkannya ke dalam aplikasi.
Menambahkan Action Button ke App Bar
Buka file main.dart lalu tambahkan dua baris kode berikut:
import 'components/theme_button.dart';
import 'components/color_button.dart';
Lalu, cari `// TODO: Tambahkan action buttons dan timpa dengan kode berikut:
actions: [
ThemeButton(
changeThemeMode: changeThemeMode,
),
ColorButton(
changeColor: changeColor,
colorSelected: colorSelected,
),
],
Setelah hot restart, perhatikan sekarang ada dua tombol di pojok kanan atas. Coba ubah tema dan warna dengan kedua tombol tersebut.

Selanjutnya, kamu akan mempelajari salah satu aspek penting dalam membangun aplikasi — memahami struktur aplikasi mana yang sebaiknya digunakan.
Memahami Struktur Aplikasi dan navigasi
Menentukan struktur aplikasi sejak awal sangat penting untuk pengalaman pengguna. Menerapkan struktur navigasi yang tepat akan memudahkan pengguna dalam menjelajahi informasi yang ada di aplikasi kamu.
Yummy menggunakan widget Scaffold sebagai struktur awal aplikasinya. Scaffold merupakan salah satu widget Material yang paling sering digunakan di Flutter. Selanjutnya, kamu akan mempelajari cara mengimplementasikannya di dalam aplikasi kamu.
Menggunakan Scaffold
Widget Scaffold mengimplementasikan semua kebutuhan dasar struktur layout visual aplikasi kamu. Ia tersusun dari beberapa bagian berikut:
• AppBar • BottomSheet • BottomNavigationBar • Drawer • FloatingActionButton • SnackBar
Scaffold menyediakan banyak fungsionalitas secara out of the box.
Diagram berikut merepresentasikan beberapa komponen yang telah disebutkan sebelumnya, sekaligus menampilkan opsi navigasi kiri dan kanan:

Untuk informasi lebih lanjut, lihat dokumentasi Flutter tentang widget Material Components, termasuk struktur aplikasi dan navigasi, di: https://flutter.dev/docs/development/ui/widgets/material
Sekarang, saatnya menambahkan lebih banyak fitur.
Menyiapkan Widget Home
Saat kamu mulai membangun aplikasi berskala besar, kamu akan menyusun sebuah “tangga” widget. Widget yang tersusun dari banyak widget lain bisa menjadi sangat panjang dan berantakan. Karena itu, memecah widget ke dalam file terpisah adalah praktik yang baik demi keterbacaan kode.
Untuk menghindari kode yang terlalu rumit, sekarang kamu akan membuat file terpisah yang pertama.
Langkah selanjutnya adalah memindahkan kode dari main.dart ke sebuah StatefulWidget baru bernama Home. Di dalam direktori lib, buat file baru bernama home.dart, lalu tambahkan kode berikut:
import 'package:flutter/material.dart';
import 'components/theme_button.dart';
import 'components/color_button.dart';
import 'constants.dart';
class Home extends StatefulWidget {
const Home({
super.key,
required this.changeTheme,
required this.changeColor,
required this.colorSelected,
});
final void Function(bool useLightMode) changeTheme;
final void Function(int value) changeColor;
final ColorSelection colorSelected;
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
// TODO: Catat tab aktif
// TODO: Tentukan destinasi tab
@override
Widget build(BuildContext context) {
// TODO: Tentukan halaman-halamannya
return Scaffold(
appBar: AppBar(
elevation: 4.0,
backgroundColor: Theme.of(context).colorScheme.background,
actions: [
ThemeButton(
changeThemeMode: widget.changeTheme,
),
ColorButton(
changeColor: widget.changeColor,
colorSelected: widget.colorSelected,
),
],
),
// TODO: Pindah antar halaman
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'You Hungry?!',
style: Theme.of(context).textTheme.displayLarge,
),
),
// TODO: Tambahkan bottom navigation bar
);
}
}
Kamu hanya menyalin isi Scaffold di dalam main.dart ke widget baru di home.dart.
Ingat, jika kamu merasa widget tree mulai terasa besar, sebaiknya pecah menjadi beberapa widget terpisah.
Sekarang kembali ke main.dart untuk memperbaruinya sehingga kita bisa memanggil widget Home yang baru dibuat. Di bagian atas, tambahkan perintah berikut:
import 'home.dart';
Berikutnya cari TODO: Timpa Scaffold dengan widget Home untuk kita ganti home: Scaffold(...) dengan kode berikut:
home: Home(
changeTheme: changeThemeMode,
changeColor: changeColor,
colorSelected: colorSelected,
),
Terkahir, hapus kode berikut dari main.dart karena sudah tidak terpakai lagi:
import 'components/theme_button.dart';
import 'components/color_button.dart';
Berikutnya kita akan mengatur bottom navigation.
Menambahkan Bottom Navigation Bar
Buka home.dart, cari // TODO: Catat tab aktif lalu ganti dengan kode berikut:
int tab = 0;
Properti tab dipakai untuk mencatat posisi tab yang sekarang sedang aktif atau ditampilkan.
Langkah berikutnya adalah menentukan tab yang bisa dipakai pengguna untuk pindah halaman. Cari // TODO: Tentukan halaman-halamannya lalu ganti dengan:
List<NavigationDestination> appBarDestinations = const [
NavigationDestination(
icon: Icon(Icons.credit_card),
label: 'Category',
selectedIcon: Icon(Icons.credit_card),
),
NavigationDestination(
icon: Icon(Icons.credit_card),
label: 'Post',
selectedIcon: Icon(Icons.credit_card),
),
NavigationDestination(
icon: Icon(Icons.credit_card),
label: 'Restaurant',
selectedIcon: Icon(Icons.credit_card),
),
];
Sekarang kita akan punya tiga tab yang memiliki identitasnya masing-masing.
Terkahir, cari komentar // TODO: Tambahkan bottom navigation bar lalu ganti dengan kode berikut:
// 1
bottomNavigationBar: NavigationBar(
// 2
selectedIndex: tab,
// 3
onDestinationSelected: (index) {
setState(() {
tab = index;
});
},
// 4
destinations: appBarDestinations,
),
Berikut cara kerja kode tersebut:
- Menetapkan
NavigationBarke propertibottomNavigationBar. - Menentukan tab yang aktif menggunakan
selectedIndex. - Memperbarui tab aktif sesuai pilihan pengguna.
- Menentukan daftar tab melalui
appBarDestinations.
Setelah semua langkah ini selesai, tampilan aplikasi seharusnya terlihat seperti berikut:

Sekarang setelah menyiapkan bottom navigation bar, kita perlu mengimplementasi proses pindah halamannya.
Navigasi Antar Halaman
Untuk melakukan navigasi antar halaman, pertama kita perlu menentukan daftar halaman yang bisa dikunjungi pengguna. Masih di home.dart, cari //TODO: Tentukan halaman-halamannya lalu timpa dengan kode berikut:
final pages = [
// TODO: Ganti dengan Category Card
Container(color: Colors.red),
// TODO: Ganti dengan Post Card
Container(color: Colors.green),
// TODO: Ganti dengan Restaurant Landscape Card
Container(color: Colors.blue)
];
Saat ini kita memberikan beberapa Container sebagai sebuah halaman kosong dengan warna yang berbeda. Kita akan ganti dengan konten yang sesuai nantinya.
Selanjutnya, cari // TODO: Pindah Antar Halaman lalu ganti semua isi body: Padding(...) dengan:
body: IndexedStack(
index: tab,
children: pages,
),
Widget IndexedStack akan menampilkan widget satu persatu berdasarkan indeks dengan menyimpan state masing-masing wdiget yang ada di stack tersebut.
Setelah hot restart aplikasi kita akan terlihat seperti ini:

Setelah menyiapkan navigasi tab, sekarang waktunya membuat card!