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. Operasi insertAfter, insertBefore, dan unlink berjalan 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:

OperasiListSetMapQueueLinkedList
Akses by indexO(1)O(n)
contains / containsKeyO(n)O(1)O(1)O(n)O(n)
add / addLastO(1)*O(1)O(1)O(1)*O(1)
insert di awalO(n)O(1)O(1)**
remove dari awalO(n)O(1)O(1)O(1)O(1)**
remove from tengahO(n)O(1)O(1)O(n)O(1)**
Iterasi semuaO(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 contains O(1).
  • Map digunakan untuk memetakan key ke value — pengecekan containsKey dan 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.

← Sebelumnya: Fitur Dart 3   Berikutnya: OOP di Dart →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact