Collections #
Collections adalah fondasi dari hampir setiap program nyata. Di Dart, terdapat beragam jenis collection — masing-masing dengan karakteristik performa yang berbeda dan cocok untuk use case yang berbeda pula. Menguasai collections berarti menguasai cara menyimpan, mengorganisasi, dan memproses data secara efisien di aplikasi Flutter.
Iterable — Fondasi Semua Collections #
Sebelum masuk ke jenis-jenis collection, penting memahami Iterable<T> — interface yang menjadi dasar hampir semua collection di Dart. Setiap collection yang bisa di-loop dengan for-in mengimplementasikan Iterable.
// Semua ini mengimplementasikan Iterable:
List<int> list = [1, 2, 3];
Set<String> set = {'a', 'b', 'c'};
Map<String, int>.values; // values dari Map adalah Iterable
// Semua bisa di-loop dengan for-in:
for (final item in list) { print(item); }
for (final item in set) { print(item); }
// Dan semua memiliki operasi Iterable yang sama:
list.map(...), list.where(...), list.reduce(...), list.fold(...)
List — Urutan Terindeks #
List<T> adalah collection paling umum di Dart — koleksi elemen berurutan yang bisa diakses berdasarkan indeks. List di Dart mirip dengan array di bahasa lain, tapi dengan API yang jauh lebih kaya.
Membuat List #
// List literal
final buah = ['apel', 'mangga', 'jeruk']; // List<String> (inferred)
final angka = <int>[1, 2, 3, 4, 5]; // explicit type
// List kosong
final kosong = <String>[];
final kosong2 = List<String>.empty(growable: true);
// List dengan nilai awal
final tigaNol = List.filled(3, 0); // [0, 0, 0]
final kuadrat = List.generate(5, (i) => i * i); // [0, 1, 4, 9, 16]
// Const list (immutable, compile-time constant)
const tetap = ['satu', 'dua', 'tiga']; // tidak bisa diubah
Operasi Dasar List #
final items = ['a', 'b', 'c', 'd', 'e'];
// Akses elemen
print(items[0]); // a (indeks pertama)
print(items.last); // e
print(items.first); // a
print(items.length); // 5
// Modifikasi
items.add('f'); // tambah di akhir
items.addAll(['g', 'h']); // tambah banyak
items.insert(0, 'z'); // sisipkan di indeks tertentu
items.remove('b'); // hapus nilai pertama yang cocok
items.removeAt(0); // hapus berdasarkan indeks
items.removeWhere((s) => s.compareTo('d') > 0); // hapus berdasarkan kondisi
// Pencarian
print(items.contains('c')); // true
print(items.indexOf('c')); // indeks dari 'c'
print(items.indexWhere((s) => s.length > 1)); // indeks kondisi
// Pengurutan
items.sort(); // sort default (ascending)
items.sort((a, b) => b.compareTo(a)); // sort descending
Sublist dan Spread #
final numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Sublist
final pertiga = numbers.sublist(0, 3); // [1, 2, 3]
final tengah = numbers.sublist(3, 7); // [4, 5, 6, 7]
// Spread operator -- menggabungkan list
final a = [1, 2, 3];
final b = [4, 5, 6];
final gabung = [...a, ...b]; // [1, 2, 3, 4, 5, 6]
// Null-aware spread
List<int>? mungkinNull;
final aman = [0, ...?mungkinNull, 99]; // [0, 99] -- tidak crash
Collection if dan for #
bool showAdmin = true;
List<String> extraItems = ['bonus1', 'bonus2'];
final menu = [
'Home',
'Profile',
if (showAdmin) 'Admin Panel', // conditional element
if (showAdmin) ...[ // conditional spread
'User Management',
'Settings',
],
for (final item in extraItems) item, // loop element
for (var i = 1; i <= 3; i++) 'Item $i', // loop inline
];
// ['Home', 'Profile', 'Admin Panel', 'User Management', 'Settings',
// 'bonus1', 'bonus2', 'Item 1', 'Item 2', 'Item 3']
Collection if sangat umum di Flutter! Pola if (kondisi) Widget() di dalam List children adalah cara paling idiomatic untuk menampilkan widget secara kondisional tanpa harus menggunakan operator ternary yang panjang.Set — Koleksi Unik Tanpa Duplikat #
Set<T> adalah collection yang hanya menyimpan elemen unik — tidak ada duplikat. Operasi contains, add, dan remove pada Set umumnya berjalan dalam waktu O(1) — jauh lebih cepat dari List untuk pengecekan keberadaan elemen.
// Set literal -- gunakan kurung kurawal
final hewan = {'kucing', 'anjing', 'burung'};
// Perbedaan kunci vs Map literal (juga pakai kurawal):
final map = {'kunci': 'nilai'}; // Map (ada colon)
final set = {'a', 'b', 'c'}; // Set (tidak ada colon)
final emptySet = <String>{}; // Set kosong (wajib explicit type)
final emptyMap = {}; // Map kosong (default)
// Duplikat otomatis dihapus
final unik = {'a', 'b', 'a', 'c', 'b'};
print(unik); // {a, b, c}
// Operasi dasar
hewan.add('ikan');
hewan.addAll(['kelinci', 'hamster']);
hewan.remove('burung');
print(hewan.contains('kucing')); // true
print(hewan.length); // 5
Set Operations — Union, Intersection, Difference #
Ini adalah kekuatan utama Set yang tidak dimiliki List:
final programmer = {'Alice', 'Bob', 'Charlie', 'Diana'};
final designer = {'Charlie', 'Diana', 'Eve', 'Frank'};
// Union -- semua elemen dari kedua set
final semua = programmer.union(designer);
// {Alice, Bob, Charlie, Diana, Eve, Frank}
// Intersection -- elemen yang ada di KEDUANYA
final keduanya = programmer.intersection(designer);
// {Charlie, Diana}
// Difference -- elemen di programmer tapi TIDAK di designer
final hanyaProgrammer = programmer.difference(designer);
// {Alice, Bob}
Implementasi Set yang Berbeda #
import 'dart:collection';
// Set default (LinkedHashSet) -- menjaga insertion order
final ordered = {'c', 'a', 'b'};
print(ordered); // {c, a, b} -- urutan terjaga
// HashSet -- TIDAK menjaga urutan, tapi lebih cepat
final unordered = HashSet<String>.from(['c', 'a', 'b']);
print(unordered); // bisa {a, b, c} atau urutan apapun
// SplayTreeSet -- selalu terurut berdasarkan comparator
final sorted = SplayTreeSet<String>((a, b) => a.compareTo(b));
sorted.addAll(['banana', 'apple', 'cherry']);
print(sorted); // {apple, banana, cherry} -- selalu sorted
Map — Pasangan Key-Value #
Map<K, V> adalah collection yang memetakan key ke value. Setiap key bersifat unik. Dart menggunakan LinkedHashMap secara default — menjaga insertion order dan akses O(1) rata-rata.
// Map literal
final nilai = {
'matematika': 90,
'bahasa': 85,
'ipa': 92,
};
// Akses value
print(nilai['matematika']); // 90
print(nilai['fisika']); // null -- key tidak ada
print(nilai['fisika'] ?? 0); // 0 -- dengan default value
// Modifikasi
nilai['fisika'] = 88; // tambah/update
nilai.remove('bahasa'); // hapus entry
// Iterasi
nilai.forEach((key, value) {
print('$key: $value');
});
for (final entry in nilai.entries) {
print('${entry.key}: ${entry.value}');
}
// Keys dan Values
print(nilai.keys.toList()); // [matematika, ipa, fisika]
print(nilai.values.toList()); // [90, 92, 88]
// Pengecekan
print(nilai.containsKey('matematika')); // true
print(nilai.containsValue(92)); // true
Map.update, putIfAbsent, updateAll #
final stok = {'apel': 10, 'mangga': 5};
// update: ubah value berdasarkan nilai sebelumnya
stok.update('apel', (v) => v + 5); // apel: 15
stok.update('jeruk', (v) => v + 1,
ifAbsent: () => 1); // jeruk: 1 (baru)
// putIfAbsent: tambahkan hanya jika key belum ada
stok.putIfAbsent('anggur', () => 20); // anggur: 20
stok.putIfAbsent('apel', () => 999); // apel tetap 15
// updateAll: update semua value sekaligus
stok.updateAll((key, value) => value * 2); // semua digandakan
Map dari dan ke Collections Lain #
final produk = [
Product(id: '1', nama: 'Apel', harga: 5000),
Product(id: '2', nama: 'Mangga', harga: 8000),
Product(id: '3', nama: 'Jeruk', harga: 6000),
];
// List --> Map menggunakan Map.fromIterable
final productMap = Map.fromIterable(
produk,
key: (p) => p.id,
value: (p) => p,
);
// Atau dengan Map.fromEntries (lebih idiomatis)
final productMap2 = Map.fromEntries(
produk.map((p) => MapEntry(p.id, p)),
);
// Grouping: List --> Map<K, List<V>>
final byCategory = <String, List<Product>>{};
for (final p in produk) {
byCategory.putIfAbsent(p.kategori, () => []).add(p);
}
Queue — FIFO Collection #
Queue<T> adalah collection yang dioptimalkan untuk operasi di kedua ujung — menambah dan menghapus di awal atau akhir dalam waktu O(1). Cocok untuk implementasi antrean (FIFO) atau stack (LIFO).
import 'dart:collection';
final queue = Queue<String>();
// Tambah elemen
queue.add('pertama'); // tambah di akhir
queue.addLast('kedua'); // sama dengan add()
queue.addFirst('sebelum'); // tambah di awal
queue.addAll(['ketiga', 'keempat']);
print(queue); // {sebelum, pertama, kedua, ketiga, keempat}
// Hapus elemen
final depan = queue.removeFirst(); // 'sebelum' -- FIFO
final belakang = queue.removeLast(); // 'keempat'
print(depan); // sebelum
print(belakang); // keempat
// Peek tanpa menghapus
print(queue.first); // elemen pertama (tanpa hapus)
print(queue.last); // elemen terakhir (tanpa hapus)
Kapan Menggunakan Queue vs List #
List:
✓ Akses random berdasarkan indeks (O(1))
✓ Sorting, searching, filtering
✗ insert/remove di awal lambat (O(n))
Queue:
✓ Tambah/hapus di awal dan akhir sangat cepat (O(1))
✓ Implementasi antrean (FIFO) atau stack (LIFO)
✗ Tidak ada akses by index
✗ Tidak bisa di-sort langsung
LinkedList — Doubly Linked List #
LinkedList<E> dari dart:collection adalah implementasi doubly linked list. Berbeda dari nama yang mungkin menyesatkan, ia tidak mengimplementasikan List — ia adalah struktur terpisah. Setiap elemen harus extend LinkedListEntry<E>.
import 'dart:collection';
// Elemen harus extend LinkedListEntry
class TaskEntry extends LinkedListEntry<TaskEntry> {
final String nama;
bool selesai;
TaskEntry(this.nama, {this.selesai = false});
@override
String toString() => '[${ selesai ? '✓' : ' '}] $nama';
}
void main() {
final tasks = LinkedList<TaskEntry>();
final task1 = TaskEntry('Belajar Dart');
final task2 = TaskEntry('Buat UI Flutter');
final task3 = TaskEntry('Tulis Tests');
tasks.addAll([task1, task2, task3]);
// Operasi O(1) jika kamu punya referensi ke entry-nya
task2.insertAfter(TaskEntry('Review PR')); // sisip setelah task2
task1.insertBefore(TaskEntry('Setup env')); // sisip sebelum task1
for (final task in tasks) {
print(task);
}
// Unlink (hapus) tanpa perlu mencari terlebih dahulu -- O(1)!
task2.unlink();
}
LinkedList cocok untuk kasus di mana kamu sering menyisipkan atau menghapus elemen di tengah-tengah collection dan kamu sudah punya referensi ke entry yang ingin dimanipulasi. OperasiinsertAfter,insertBefore, danunlinkberjalan O(1) — berbeda dengan List biasa yang O(n) untuk operasi serupa.
Operasi Fungsional pada Collections #
Semua collection yang mengimplementasikan Iterable memiliki serangkaian operasi fungsional yang sangat berguna:
map, where, reduce, fold #
final angka = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// map -- transformasi setiap elemen
final kuadrat = angka.map((n) => n * n).toList();
// [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
// where -- filter elemen yang memenuhi kondisi
final genap = angka.where((n) => n % 2 == 0).toList();
// [2, 4, 6, 8, 10]
// reduce -- agregasi (membutuhkan minimal 1 elemen)
final jumlah = angka.reduce((sum, n) => sum + n);
// 55
// fold -- seperti reduce tapi dengan initial value
final jumlahFold = angka.fold(0, (sum, n) => sum + n);
// 55 (sama, tapi aman untuk list kosong)
// expand (flatMap) -- satu elemen jadi banyak
final kata = ['Halo Dunia', 'Flutter Dart'];
final huruf = kata.expand((s) => s.split(' ')).toList();
// ['Halo', 'Dunia', 'Flutter', 'Dart']
any, every, firstWhere, lastWhere #
final scores = [75, 82, 91, 67, 88, 95, 73];
// any -- true jika MINIMAL SATU memenuhi kondisi
print(scores.any((s) => s >= 90)); // true (ada 91 dan 95)
// every -- true jika SEMUA memenuhi kondisi
print(scores.every((s) => s >= 60)); // true (semua >= 60)
// firstWhere -- elemen pertama yang memenuhi kondisi
final lulus = scores.firstWhere(
(s) => s >= 90,
orElse: () => -1, // jika tidak ada yang cocok
);
print(lulus); // 91
// lastWhere -- elemen terakhir yang memenuhi kondisi
final lulusTerakhir = scores.lastWhere((s) => s >= 90);
print(lulusTerakhir); // 95
take, skip, dan Lazy Evaluation #
final stream = Iterable.generate(1000000, (i) => i); // 0..999999
// take dan skip menggunakan lazy evaluation -- efisien!
final sepuluhPertama = stream.take(10).toList();
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -- hanya proses 10 elemen!
final skipSeratus = stream.skip(100).take(5).toList();
// [100, 101, 102, 103, 104]
// Lazy chaining -- tidak ada iterasi sampai toList() atau forEach()
final hasil = stream
.where((n) => n % 2 == 0) // lazy
.map((n) => n * 3) // lazy
.take(5) // lazy
.toList(); // BARU DI SINI iterasi terjadi
// [0, 6, 12, 18, 24]
Tabel Kompleksitas Operasi #
Panduan memilih collection berdasarkan kebutuhan performa:
| Operasi | List | Set | Map | Queue | LinkedList |
|---|---|---|---|---|---|
| Akses by index | O(1) | ✗ | ✗ | ✗ | O(n) |
contains / containsKey | O(n) | O(1) | O(1) | O(n) | O(n) |
add / addLast | O(1)* | O(1) | O(1) | O(1)* | O(1) |
insert di awal | O(n) | ✗ | ✗ | O(1) | O(1)** |
remove dari awal | O(n) | O(1) | O(1) | O(1) | O(1)** |
remove from tengah | O(n) | O(1) | O(1) | O(n) | O(1)** |
| Iterasi semua | O(n) | O(n) | O(n) | O(n) | O(n) |
*amortized (bisa O(n) saat resize buffer) **hanya O(1) jika sudah punya referensi ke entry
Ringkasan #
- List adalah collection paling umum — gunakan saat butuh urutan dengan akses by index dan operasi fungsional yang kaya.
- Set adalah pilihan terbaik untuk menyimpan elemen unik dan operasi himpunan (union, intersection, difference) — pengecekan
containsO(1).- Map digunakan untuk memetakan key ke value — pengecekan
containsKeydan akses by key O(1).- Queue dioptimalkan untuk FIFO/LIFO — tambah/hapus di kedua ujung O(1).
- LinkedList cocok untuk insert/delete di tengah yang sangat sering, saat kamu memegang referensi ke entry yang ingin dimanipulasi — O(1) untuk insertAfter/insertBefore/unlink.
- Collection if dan for dalam literal adalah cara paling idiomatic Flutter untuk membangun list widget secara kondisional.
- Operasi fungsional (
map,where,reduce,fold,expand) bersifat lazy — tidak dievaluasi sampai dibutuhkan, membuatnya efisien untuk dataset besar.