Pemrograman Dart
Fungsi sebagai Nilai, Anonymous Function, dan Manipulasi Koleksi
Kuasai konsep first-class function, anonymous function (lambda), closure, serta cara memfilter dan mentransformasi koleksi secara deklaratif di Dart 3.

Pada materi sebelumnya, kita telah menulis fungsi bernama untuk membungkus logika dan memanggilnya secara langsung. Namun, ketika mengolah data koleksi seperti daftar produk, transaksi, atau nilai mahasiswa, kita sering kali hanya butuh mengirimkan sepotong logika singkat ke dalam operasi lain—misalnya "ambil produk yang harganya di bawah Rp100.000" atau "ubah daftar harga menjadi harga setelah pajak". Jika kita harus membuat fungsi bernama baru untuk setiap kriteria kecil dan menulis perulangan for manual beserta variabel penampung sementara setiap saat, kode kita akan menjadi panjang dan bertele-tele. Dart mengatasi hal ini dengan memperlakukan fungsi sebagai nilai (first-class object) dan menyediakan anonymous function untuk memanipulasi koleksi secara deklaratif.
Yang akan dipelajari
- Memahami konsep fungsi sebagai first-class object (menyimpan fungsi ke variabel dan mengirimkannya sebagai parameter).
- Menulis anonymous function (lambda) untuk operasi singkat tanpa nama.
- Memahami cara kerja lexical scope dan closure secara intuitif.
- Mengolah
List,Set, danMapsecara deklaratif menggunakan.where(),.map(),.forEach(),.any(),.every(), dan.fold().
Fungsi sebagai first-class object dan anonymous function
Di dalam bahasa Dart, fungsi memiliki kedudukan yang setara dengan tipe data lain seperti int, String, atau List. Artinya, sebuah fungsi dapat:
- Disimpan ke dalam sebuah variabel.
- Dikirimkan sebagai argumen ke fungsi lain (higher-order function).
- Dikembalikan sebagai hasil (return value) dari fungsi lain.
Ketika kita hanya membutuhkan sebuah fungsi sekali pakai untuk dikirim sebagai argumen, kita tidak perlu memberinya nama. Fungsi tanpa nama ini disebut anonymous function atau lambda.
| Bentuk Fungsi | Contoh Penulisan | Kapan Digunakan |
|---|---|---|
| Fungsi bernama biasa | int kaliDua(int x) { return x * 2; } |
Logika dipakai berulang kali di berbagai bagian program. |
| Anonymous function blok | (int x) { return x * 2; } |
Fungsi sekali pakai yang terdiri dari beberapa baris pernyataan. |
Anonymous function panah (=>) |
(int x) => x * 2 atau (x) => x * 2 |
Callback singkat satu ekspresi (misalnya di dalam .where() atau .map()). |
Mari kita lihat bagaimana fungsi disimpan ke dalam variabel bertipe Function dan dikirimkan sebagai parameter ke fungsi lain:
// Mendefinisikan fungsi yang menerima fungsi lain (operasi) sebagai parameter
int terapkanOperasi(int a, int b, int Function(int, int) operasi) {
return operasi(a, b);
}
int tambah(int x, int y) => x + y;
void main() {
// 1. Menyimpan referensi fungsi bernama ke dalam variabel (tanpa tanda kurung ())
int Function(int, int) hitung = tambah;
print('Hasil tambah: ${hitung(10, 5)}');
// 2. Mengirim fungsi bernama sebagai argumen
print('Terapkan tambah: ${terapkanOperasi(20, 8, tambah)}');
// 3. Mengirim anonymous function (lambda) langsung sebagai argumen
int hasilKali = terapkanOperasi(20, 8, (a, b) => a * b);
print('Terapkan kali : $hasilKali');
int hasilSelisih = terapkanOperasi(20, 8, (a, b) {
int selisih = a - b;
return selisih.abs();
});
print('Terapkan selisih: $hasilSelisih');
}
Keluaran program:
Hasil tambah: 15
Terapkan tambah: 28
Terapkan kali : 160
Terapkan selisih: 12
Mengenal lexical scope dan closure
Di Dart, cakupan variabel (scope) ditentukan oleh letak kurung kurawal {...} tempat variabel tersebut ditulis. Fungsi yang didefinisikan di dalam fungsi lain dapat mengakses variabel milik fungsi induknya. Bahkan ketika fungsi dalam tersebut dikembalikan dan dipanggil di luar, ia tetap "mengingat" variabel di sekitarnya—kemampuan inilah yang disebut closure:
// Fungsi yang mengembalikan fungsi baru (closure)
int Function(int) buatPengali(int faktor) {
// Anonymous function di bawah ini "mengingat" variabel 'faktor'
return (int nilai) => nilai * faktor;
}
void main() {
var kaliTiga = buatPengali(3);
var kaliSepuluh = buatPengali(10);
print(kaliTiga(7)); // 7 * 3 = 21
print(kaliSepuluh(7)); // 7 * 10 = 70
}
Keluaran program:
21
70
Manipulasi koleksi secara deklaratif
Kekuatan utama anonymous function akan sangat terasa ketika digabungkan dengan method bawaan koleksi (Iterable / List). Alih-alih menulis perulangan for manual yang menjelaskan bagaimana cara menelusuri indeks demi indeks (imperatif), kita cukup menyatakan apa hasil yang kita inginkan (deklaratif).
| Method Koleksi | Fungsi Utama | Nilai Kembalian |
|---|---|---|
.where((e) => kondisi) |
Menyaring elemen yang memenuhi kondisi (true). |
Iterable<T> (gunakan .toList() jika butuh List) |
.map((e) => bentukBaru) |
Mengubah setiap elemen menjadi nilai atau tipe baru. | Iterable<R> (gunakan .toList() jika butuh List) |
.forEach((e) { ... }) |
Menjalankan aksi untuk setiap elemen koleksi. | void |
.any((e) => kondisi) |
Mengecek apakah minimal ada satu elemen yang memenuhi kondisi. | bool |
.every((e) => kondisi) |
Mengecek apakah semua elemen memenuhi kondisi. | bool |
.fold(nilaiAwal, (akumulasi, e) => ...) |
Menggabungkan seluruh elemen menjadi satu nilai akhir (mis. total harga). | Nilai bertipe sesuai nilaiAwal |
Perhatikan bagaimana kita dapat merangkai (chaining) .where(), .map(), dan .fold() untuk mengolah data transaksi toko buku:
void main() {
List<Map<String, dynamic>> keranjang = [
{'judul': 'Dasar Pemrograman Dart', 'harga': 95000, 'stokTersedia': true},
{'judul': 'Algoritma & Struktur Data', 'harga': 130000, 'stokTersedia': true},
{'judul': 'Arsitektur Aplikasi Flutter', 'harga': 150000, 'stokTersedia': false},
{'judul': 'Panduan Git untuk Pemula', 'harga': 65000, 'stokTersedia': true},
];
// 1. Memeriksa kondisi dengan .any() dan .every()
bool semuaTersedia = keranjang.every((item) => item['stokTersedia'] == true);
bool adaBukuMahal = keranjang.any((item) => (item['harga'] as int) > 140000);
print('Semua buku ready stock? $semuaTersedia');
print('Ada buku di atas Rp140.000? $adaBukuMahal\n');
// 2. Menyaring (.where) buku yang tersedia, lalu mengubahnya (.map) menjadi teks ringkas
List<String> daftarSiapKirim = keranjang
.where((item) => item['stokTersedia'] == true)
.map((item) => '${item['judul']} (Rp${item['harga']})')
.toList();
print('Buku siap dikirim:');
daftarSiapKirim.forEach((info) => print('- $info'));
// 3. Menghitung total harga buku yang tersedia menggunakan .fold()
int totalBayar = keranjang
.where((item) => item['stokTersedia'] == true)
.fold(0, (int totalSementara, item) => totalSementara + (item['harga'] as int));
print('\nTotal yang harus dibayar: Rp$totalBayar');
}
Keluaran program:
Semua buku ready stock? false
Ada buku di atas Rp140.000? true
Buku siap dikirim:
- Dasar Pemrograman Dart (Rp95000)
- Algoritma & Struktur Data (Rp130000)
- Panduan Git untuk Pemula (Rp65000)
Total yang harus dibayar: Rp290000
Perhatikan pemanggilan .toList() di akhir rangkaian .where(...).map(...). Di Dart, method .where() dan .map() mengembalikan Iterable yang bersifat lazy (baru dihitung ketika elemennya benar-benar diakses). Memanggil .toList() akan langsung mengeksekusi transformasi tersebut dan menyimpannya ke dalam objek List baru tanpa mengubah List aslinya.
Kaitannya dengan Flutter / Dunia Nyata: Di Flutter, Anda akan menggunakan
.map()dan.toList()hampir setiap hari untuk mengubah daftar data dari server menjadi daftar komponen UI (widget), misalnya:children: daftarProduk.map((produk) => KartuProduk(data: produk)).toList(). Selain itu, parameter aksi sepertionPressed: () { ... }pada tombol atauonChanged: (teks) => ...pada kolom input teks semuanya menggunakan anonymous function.
Coba sendiri
Buka DartPad dan salin contoh kode keranjang di atas, lalu coba modifikasi berikut:
- Tambahkan syarat kedua pada
.where()di bagiandaftarSiapKirimagar hanya menyaring buku yangstokTersedia == truedan harganya di bawah100000. - Gunakan
.map()untuk memberikan diskon 10% pada setiap buku yang tersedia, lalu hitung total barunya menggunakan.fold(). - Coba hapus
.toList()pada variabeldaftarSiapKirim(ubah tipe variabelnya menjadivar) lalu cetakprint(daftarSiapKirim). Perhatikan bahwa hasil cetaknya menggunakan tanda kurung biasa(...)milikIterable, bukan kurung siku[...]milikList.
Kesalahan yang sering terjadi
| Kesalahan Umum | Penyebab | Solusi yang Benar |
|---|---|---|
Menulis hitung = tambah(); saat ingin menyimpan fungsi ke variabel |
Menambahkan tanda kurung () berarti memanggil fungsi tersebut dan menyimpan hasil kembaliannya, bukan menyimpan objek fungsinya. |
Tulis nama fungsinya saja tanpa kurung: var hitung = tambah; (function tear-off). |
| Error tipe data: A value of type 'Iterable' can't be assigned to a variable of type 'List' | Method .map() dan .where() mengembalikan Iterable<T>, bukan List<T>. |
Tambahkan .toList() di ujung pemanggilan: koleksi.map(...).toList(). |
Mengira .map() atau .where() mengubah isi List asli secara langsung |
Kedua method tersebut menghasilkan koleksi baru dan membiarkan List sumber tetap utuh (immutable operation). |
Simpan hasil kembaliannya ke variabel baru atau timpa variabel lama jika memang diinginkan. |
Lupa menulis return saat menggunakan kurung kurawal { ... } di dalam .map() atau .where() |
Jika menggunakan { ... } (bukan =>), fungsi tanpa return akan mengembalikan null. |
Gunakan => ekspresi untuk satu baris, atau pastikan ada pernyataan return nilai; di dalam blok { ... }. |
Latihan
Kerjakan tiga soal latihan berikut di DartPad:
- Filter dan Transformasi Nilai Ujian: Diberikan
List<int> nilaiMentah = [45, 78, 82, 60, 90, 55, 88, 72];. Gunakan.where()untuk menyaring nilai yang lulus (>= 75), lalu gunakan.map()untuk menambahkan bonus 5 poin ke setiap nilai yang lulus tersebut, dan simpan hasilnya sebagaiList<int>. - Fungsi Generator Diskon (Closure): Buatlah fungsi
double Function(double) buatKalkulatorDiskon(double persenDiskon)yang mengembalikan anonymous function. Fungsi yang dikembalikan tersebut menerima parameterdouble hargaAwaldan mengembalikan harga setelah dipotongpersenDiskon. Uji dengan membuat variabeldiskonMember = buatKalkulatorDiskon(20)dandiskon Musiman = buatKalkulatorDiskon(10). - Rekapitulasi Pengeluaran Harian (
.fold&.any): Diberikan daftar pengeluaran berupaList< (String nama, int nominal, String kategori) >(menggunakan Record Dart 3). Gunakan.any()untuk mengecek apakah ada pengeluaran tunggal di atasRp500.000, lalu gunakan.where()dan.fold()untuk menghitung total pengeluaran khusus kategori'Makanan'.
Kunci jawaban dan pembahasan
Latihan 1
void main() {
List<int> nilaiMentah = [45, 78, 82, 60, 90, 55, 88, 72];
List<int> nilaiLulusPlusBonus = nilaiMentah
.where((nilai) => nilai >= 75)
.map((nilai) => nilai + 5)
.toList();
print('Nilai awal : $nilaiMentah');
print('Nilai lulus + bonus 5 : $nilaiLulusPlusBonus');
}
Pembahasan: Method .where((nilai) => nilai >= 75) menyaring elemen menjadi (78, 82, 90, 88). Selanjutnya .map((nilai) => nilai + 5) mentransformasikan tiap elemen menjadi (83, 87, 95, 93), dan .toList() mengubah Iterable<int> menjadi List<int>.
Latihan 2
double Function(double) buatKalkulatorDiskon(double persenDiskon) {
return (double hargaAwal) {
double potongan = hargaAwal * (persenDiskon / 100);
return hargaAwal - potongan;
};
}
void main() {
var diskonMember = buatKalkulatorDiskon(20);
var diskonMusiman = buatKalkulatorDiskon(10);
print('Harga Rp150.000 (Member 20%) : Rp${diskonMember(150000)}');
print('Harga Rp150.000 (Musiman 10%): Rp${diskonMusiman(150000)}');
}
Pembahasan: buatKalkulatorDiskon merupakan higher-order function yang mengembalikan sebuah closure. Meskipun eksekusi buatKalkulatorDiskon(20) sudah selesai, fungsi yang disimpan di dalam diskonMember tetap menyimpan nilai persenDiskon = 20 di dalam cakupannya.
Latihan 3
void main() {
List<(String, int, String)> pengeluaran = [
('Makan Siang Kantin', 35000, 'Makanan'),
('Tiket Kereta Api', 250000, 'Transportasi'),
('Kopi & Roti', 45000, 'Makanan'),
('Servis Laptop', 600000, 'Elektronik'),
('Belanja Sayur & Lauk', 120000, 'Makanan'),
];
bool adaPengeluaranBesar = pengeluaran.any((item) => item.$2 > 500000);
int totalMakanan = pengeluaran
.where((item) => item.$3 == 'Makanan')
.fold(0, (int akumulasi, item) => akumulasi + item.$2);
print('Ada pengeluaran > Rp500.000? $adaPengeluaranBesar');
print('Total pengeluaran Makanan : Rp$totalMakanan');
}
Pembahasan: Pada setiap elemen Record (String, int, String), kita mengakses nominal melalui item.$2 dan kategori melalui item.$3. Method .where() menyaring hanya record berkategori 'Makanan', lalu .fold() menjumlahkan field $2 mulai dari nilai awal 0 hingga menghasilkan 200000.
Diskusi (0)
Kami menyimpan nama (opsional), komentar, dan hash teknis (bukan alamat IP asli) untuk mencegah spam. Cookie fungsional dipakai agar Anda dapat mengedit atau menghapus komentar sendiri dari browser ini. Untuk permintaan penghapusan, gunakan halaman kontak.
Belum ada komentar. Mulai diskusinya.