Topik ini membahas cara menyusun komponen desain yang dapat digunakan ulang pada Flutter. Komponen dibangun melalui komposisi widget, dilengkapi konstruktor dengan parameter wajib dan opsional, properti child untuk isi yang ditentukan pemanggil, serta konstruktor const untuk widget tanpa state. Materi ditutup dengan penempatan komponen pada berkas terpisah agar komponen yang sama dapat dipanggil dari beberapa layar.
Komposisi sebagai cara baku#
Cara baku untuk membuat komponen pada Flutter adalah komposisi, yakni merakit kelas widget baru dari widget-widget yang sudah ada, dan bukan mewarisi kelas widget bawaan untuk menambah perilaku. Kelas widget kustom tetap mewarisi StatelessWidget atau StatefulWidget, sedangkan tampilan dan perilakunya dirakit di dalam metode build dengan widget bawaan seperti Container, Column, dan Card. Pendekatan ini menjaga antarmuka komponen tetap terkendali, karena yang diperlihatkan ke luar hanya parameter konstruktor, sedangkan detail penyusunannya tersembunyi di dalam metode build.
Komponen seperti ini sebenarnya sudah ditulis sepanjang jalur ini, meskipun belum diberi nama komponen. Kelas LabelStatus pada modul Fondasi widget, misalnya, adalah widget kustom yang menyusun Text berdasarkan satu parameter selesai. Topik ini melangkah lebih jauh dengan menetapkan pola yang sama secara sadar, yaitu komponen yang parameter wajibnya jelas, isinya dapat ditentukan pemanggil, dan penempatannya terpisah dari layar yang memanggilnya. Menyebut sebuah widget sebagai widget kustom juga tidak menuntut teknik baru, karena widget bawaan dan widget kustom dibangun dengan cara yang sama, hanya peran dan tingkatnya yang berbeda.
Komponen KartuTugas dan Panel#
Dua komponen digunakan sebagai contoh sepanjang topik ini, yaitu KartuTugas untuk satu butir tugas dan Panel untuk membungkus isi dengan latar aksen. Keduanya ditulis pada satu berkas komponen bernama kartu_tugas.dart, dan berkas tersebut tidak berisi layar apa pun, sehingga isinya murni komponen yang siap dipanggil. Kedua nama sengaja dipilih netral agar mudah dibaca sebagai komponen, bukan sebagai layar, dan keduanya kelak dipanggil dari berkas utama aplikasi pada bagian akhir.
// tidak-dijalankan
import 'package:flutter/material.dart';
class KartuTugas extends StatelessWidget {
const KartuTugas({
super.key,
required this.judul,
required this.selesai,
this.ditekan,
});
final String judul;
final bool selesai;
final VoidCallback? ditekan;
@override
Widget build(BuildContext context) {
return Card(
child: ListTile(
leading: Icon(
selesai ? Icons.check_circle : Icons.radio_button_unchecked,
),
title: Text(judul),
onTap: ditekan,
),
);
}
}
class Panel extends StatelessWidget {
const Panel({super.key, required this.child});
final Widget child;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondaryContainer,
borderRadius: BorderRadius.circular(12),
),
child: child,
);
}
}KartuTugas menampilkan status tugas melalui ikon yang berganti mengikuti nilai selesai, sedangkan Panel membungkus child dengan latar secondaryContainer yang dibaca dari tema. Warna yang dipilih berasal dari ColorScheme, bukan dari warna tetap, sehingga kedua komponen mengikuti tema aplikasi secara otomatis. Kedua kelas juga tetap stateless karena seluruh data yang diperlukan sudah diterima melalui konstruktornya. Keputusan tampilan yang tidak berubah antarpemanggilan, seperti radius sudut dan jarak padding, ditetapkan di dalam komponen, sedangkan nilai yang berubah antarpemanggilan masuk melalui konstruktor.
Konstruktor dengan parameter wajib dan opsional#
Konstruktor menetapkan kontrak data antara komponen dan pemanggilnya, dan kontrak inilah yang mencegah komponen dipanggil tanpa data penting. Parameter wajib ditandai required, parameter opsional dibiarkan tanpa penanda dan boleh bernilai bawaan, sedangkan setiap nilai disimpan pada field final karena widget bersifat immutable. Pada KartuTugas, judul dan selesai wajib diisi, sedangkan ditekan opsional bertipe callback yang bernilai null apabila tidak diberikan.
// tidak-dijalankan
const KartuTugas({
super.key,
required this.judul,
required this.selesai,
this.ditekan,
});
final String judul;
final bool selesai;
final VoidCallback? ditekan;Pemanggilannya disesuaikan dengan kebutuhan. Tugas yang tidak perlu merespons ketukan cukup diberi parameter wajibnya saja, sedangkan tugas yang dapat ditandai selesai menerima fungsi pada ditekan.
// tidak-dijalankan
// Parameter wajib saja
KartuTugas(judul: 'Menyapu halaman', selesai: true)
// Seluruh parameter
KartuTugas(
judul: 'Menyiram tanaman',
selesai: false,
ditekan: () => tandaiSelesai(),
)Parameter ditekan memindahkan perilaku kepada pemanggil, sehingga komponen tetap stateless meskipun dapat berinteraksi. Pola ini sama dengan properti onPressed pada widget bawaan, dan analisis Dart menandai error apabila parameter wajib tidak diberikan, sehingga kontraknya dijaga sejak waktu kompilasi. Bentuk this.nama = nilai juga tersedia apabila parameter opsional membutuhkan nilai bawaan, misalnya this.tinggi = 56.0. Parameter opsional yang tidak diberi nilai bawaan bernilai null, dan tipe nullable-nya memaksa kode komponen menangani keadaan kosong tersebut secara eksplisit.
Properti child#
Properti child adalah parameter bertipe Widget yang diteruskan utuh dari pemanggil, dan tipe tersebut menerima widget apa pun, termasuk komponen kustom lain. Komponen yang menerima child mengurus latar, padding, dan bentuk, sedangkan isi ditentukan sepenuhnya oleh pemanggil. Perbedaannya dengan parameter data seperti judul terletak pada jenis nilai yang diterima, karena child menerima widget sehingga satu komponen dapat membungkus isi apa pun tanpa parameter tambahan.
// tidak-dijalankan
const Panel(child: Text('Dua tugas belum selesai pada minggu ini'))
Panel(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text('Ringkasan minggu ini'),
Text('Tiga tugas selesai, dua tertunda'),
],
),
)Nama child dipilih agar komponen mengikuti konvensi widget bawaan, sehingga cara membacanya sama dengan cara membaca Container atau Padding. Komponen yang dirancang dengan child juga lebih tahan perubahan kebutuhan, karena penambahan isi tidak pernah menuntut perubahan pada komponen itu sendiri. Pada Panel, latar dan radius tetap dipegang komponen, sedangkan pemanggil bebas menaruh teks, kolom, atau gambar di dalamnya. Aturan praktisnya, parameter data digunakan untuk nilai yang bentuknya sudah pasti, sedangkan child digunakan untuk isi yang bentuknya ditentukan pemanggil.
Konstruktor const#
Konstruktor const menjadikan instance widget sebagai konstanta waktu kompilasi, dengan syarat seluruh field final dan seluruh argumen pemanggil juga konstanta, misalnya literal teks atau konstanta lain. Kedua komponen pada topik ini memenuhi syarat tersebut, sehingga pemanggilannya boleh diberi awalan const apabila argumennya konstanta. Manfaatnya muncul saat rebuild, karena instance const yang sama tidak memicu pembangunan ulang subpohonnya, dan framework dapat melewati bagian tampilan yang tidak berubah.
// tidak-dijalankan
// Sah: seluruh argumen konstanta
const KartuTugas(judul: 'Menyapu halaman', selesai: true)
// Tidak konstanta: argumen ditekan berupa fungsi
KartuTugas(
judul: 'Menyiram tanaman',
selesai: false,
ditekan: () => tandaiSelesai(),
)Pemanggilan kedua tidak dapat diberi const karena fungsi bukan konstanta waktu kompilasi, tetapi konstruktor komponennya tetap ditandai const agar kesempatan itu terbuka bagi pemanggil lain. Kesempatan itu terwujud ketika tampilan yang sama berulang di banyak tempat, misalnya baris judul yang identik pada beberapa layar. Linter Flutter menyediakan aturan prefer_const_constructors yang menandai pemanggilan yang layak diberi const, sehingga kebiasaan ini mudah dijaga. Widget tanpa state sebaiknya selalu menyediakan konstruktor const karena syaratnya sudah terpenuhi sejak dirancang.
Menggunakan komponen dari berkas terpisah#
Komponen yang digunakan di beberapa layar ditempatkan pada berkas tersendiri di bawah direktori lib, umumnya dikelompokkan dalam subdirektori widgets. Penempatan ini membuat komponen dapat diimpor oleh layar mana pun tanpa duplikasi kode. Susunan proyek kecil untuk aplikasi jadwal piket adalah sebagai berikut.
lib/
├── main.dart
└── widgets/
└── kartu_tugas.dartBerkas yang menggunakan komponen mengimpor berkas komponen terlebih dahulu, kemudian memanggilnya seperti widget bawaan. Layar daftar pada berkas utama menggunakan kedua komponen tersebut, dan keduanya otomatis mengikuti tema aplikasi.
// tidak-dijalankan
import 'package:flutter/material.dart';
import 'widgets/kartu_tugas.dart';
void main() => runApp(const AplikasiPiket());
class AplikasiPiket extends StatelessWidget {
const AplikasiPiket({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Jadwal Piket',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
),
home: const LayarDaftar(),
);
}
}
class LayarDaftar extends StatelessWidget {
const LayarDaftar({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Jadwal Piket')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
const Panel(
child: Text('Dua tugas belum selesai pada minggu ini'),
),
const SizedBox(height: 12),
const KartuTugas(judul: 'Menyapu halaman', selesai: true),
KartuTugas(
judul: 'Menyiram tanaman',
selesai: false,
ditekan: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Tugas ditandai selesai')),
);
},
),
],
),
);
}
}Impor widgets/kartu_tugas.dart menghubungkan layar dengan kedua komponennya, dan alamat relatifnya mengikuti lokasi berkas yang mengimpor. Impor relatif seperti ini sah untuk proyek kecil, sedangkan proyek yang lebih besar umumnya memilih impor bentuk package agar pemindahan berkas tidak merusak alamat impor. Layar daftar menampilkan satu Panel sebagai ringkasan, satu kartu tanpa aksi, dan satu kartu yang memunculkan SnackBar melalui callback ditekan ketika kartu tersebut diketuk. Komponen yang sama dapat dipanggil dari layar lain tanpa menyalin kodenya, misalnya layar statistik yang menampilkan ringkasan tugas selesai, dan perubahan tampilan kartu cukup dilakukan pada satu berkas komponen.
Kesalahan umum#
Kesalahan saat menyusun komponen biasanya baru terasa ketika komponen dipanggil dari layar kedua. Tiga pola berikut paling sering muncul.
- Menyimpan konfigurasi pada field yang tidak
final. Widget bersifat immutable, dan field yang dapat diubah melanggar kontrak tersebut sehingga analisis Dart menandainya sebagai error. Perbaikannya, simpan seluruh konfigurasi pada field final yang diisi melalui konstruktor. - Menambah parameter data untuk setiap kebutuhan isi baru. Komponen dengan parameter teks, ikon, dan aksi yang terpisah cepat membengkak karena setiap tampilan baru menuntut variasi. Perbaikannya, terima
childagar bentuk isi ditentukan oleh pemanggil. - Membiarkan konstruktor tanpa penanda const. Widget tanpa state kehilangan kesempatan menjadi konstanta waktu kompilasi, sehingga subpohonnya ikut dibangun ulang pada setiap rebuild. Perbaikannya, tandai konstruktor const selama seluruh field bersifat final dan widget tidak menyimpan state.
Ringkasan#
- Widget kustom dibuat melalui komposisi, yaitu merakit widget bawaan di dalam metode build, bukan dengan mewarisi kelas widget bawaan.
- Konstruktor menetapkan kontrak data melalui parameter
requiredyang wajib dan parameter opsional yang boleh dibiarkan null atau bernilai bawaan. - Properti
childmembuat komponen dapat membungkus isi apa pun, sedangkan parameter bertipe callback memberi perilaku tanpa state internal. - Konstruktor const memungkinkan instance konstanta yang tidak ikut dibangun ulang pada rebuild.
- Komponen lintas layar ditempatkan pada berkas tersendiri di bawah direktori
libdan diimpor dari sana.
Referensi#
- Layout widgets — Flutter — Katalog widget penyusun tata letak yang menjadi bahan komposisi komponen.
- Widget catalog — Flutter — Katalog widget bawaan yang dapat dirakit menjadi komponen kustom.
- StatelessWidget class — Flutter API — Dokumentasi kelas dasar widget tanpa state beserta contoh konstruktor const-nya.