Skip to main content

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:

  1. 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.
  2. Setiap widget wajib meng-override method build().
  3. Widget Yummy dimulai dengan sebuah MaterialApp untuk memberikan tampilan dan nuansa Material Design. Untuk detail lebih lanjut, lihat https://material.io.
  4. 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.

note

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:

  1. Mengubah warna tema (misalnya Deep Purple).
  2. 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:

  1. Ubah mode light atau dark berdasarkan pilihan pengguna.
  2. 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:

  1. Widget ThemeButton akan meminta parameter berupa fungsi bernama changeThemeMode.
  2. Fungsi changeThemeMode adalah 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.
  3. Varaibel isBright adalah Boolean yang memeriksa apakah tema saat ini adalah light.
  4. Widget IconButton akan menampilkan gambar ikon light atau dark sesuai nilai dari isBright.
  5. Saat ditekan, IconButton akan mengubah tema dengan memanggil method changeThemeMode.

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:

  1. Menginisialisasi ColorButton dengan callback dan warna yang diperlukan.
  2. Properti changeColor merupakan callback untuk menangani pemilihan warna, sedangkan colorSelected adalah warna yang sedang dipilih.
  3. Membuat sebuah tombol yang menampilkan sebuah menu.
  4. Menerapkan sudut membulat pada popup menu.
  5. Menghasilkan item-item menu.
  6. Membuat daftar opsi warna dari ColorSelection.
  7. Mengonfigurasi setiap item menu dengan ikon dan teks.
  8. Memanggil changeColor saat 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.

note

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:

  1. Menetapkan NavigationBar ke properti bottomNavigationBar.
  2. Menentukan tab yang aktif menggunakan selectedIndex.
  3. Memperbarui tab aktif sesuai pilihan pengguna.
  4. 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.

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!

Membuat Custom Card