Tipe Data #
Dart adalah bahasa sound type system — setiap ekspresi memiliki tipe yang diketahui compiler, dan compiler menjamin tipe tersebut selalu benar saat runtime. Tidak ada kejutan ClassCastException seperti di Java atau TypeError seperti di JavaScript. Keunggulan ini bekerja karena Dart memiliki hierarki tipe yang kohesif: semua tipe adalah objek, semua objek mewarisi dari Object, dan sistem generics-nya memungkinkan kode yang reusable tanpa mengorbankan type safety. Artikel ini membahas setiap tipe dari sudut pandang praktis — bukan sekadar daftar definisi, tapi kapan menggunakannya, jebakan yang harus dihindari, dan method-method yang paling sering dibutuhkan.
Hierarki Tipe Dart #
Sebelum masuk ke tipe satu per satu, penting memahami gambar besarnya. Di Dart, semua tipe — termasuk int, bool, dan String — adalah kelas yang mewarisi dari Object. Tidak ada primitive type yang terpisah seperti di Java.
flowchart TD
O["Object\n(semua tipe non-null)"] --> N["num"]
O --> S["String"]
O --> B["bool"]
O --> L["List<T>"]
O --> St["Set<T>"]
O --> M["Map<K,V>"]
O --> Fn["Function"]
O --> Other["...dan lainnya"]
N --> I["int"]
N --> D["double"]
Null["Null\n(hanya null)"]
style O fill:#4f86c6,color:#fff
style Null fill:#888,color:#fff
Satu tipe yang berdiri sendiri di luar hierarki ini adalah Null — tipe yang hanya memiliki satu nilai: null. Karena Dart menggunakan sound null safety, Null tidak bisa diassign ke tipe non-nullable manapun tanpa tanda ?.
Dua tipe spesial yang perlu dipahami berbeda dari tipe biasa:
| Tipe | Posisi | Deskripsi |
|---|---|---|
dynamic |
Di luar type system | Menonaktifkan type checking — gunakan hanya jika benar-benar terpaksa |
Never |
Subtipe semua tipe | Menandai kode yang tidak pernah selesai (throw selalu, loop infinite) |
int — Bilangan Bulat
#
int di Dart mewakili bilangan bulat dengan presisi arbitrer di Dart VM (berjalan di server/CLI), dan 64-bit integer di platform yang dikompilasi ke JavaScript. Tidak ada batasan ukuran seperti int32 atau int64 di Dart VM — angka bisa sebesar memori yang tersedia.
int umur = 25;
int populasiDunia = 8_100_000_000; // underscore sebagai pemisah ribuan
int suhu = -15;
int heksadesimal = 0xFF; // 255 — literal hex
int biner = 0b1010; // 10 — literal biner
int oktal = 0o17; // 15 — literal oktal
Method dan Properti Penting #
int n = -42;
// Konversi
print(n.abs()); // 42 — nilai absolut
print(n.toDouble()); // -42.0
print(n.toString()); // '-42'
print(n.toRadixString(16)); // '-2a' — representasi hex
// Pengecekan
print(n.isNegative); // true
print(n.isEven); // true (42 genap)
print(n.isOdd); // false
// Batas
print(n.sign); // -1 (negatif), 0 (nol), 1 (positif)
print(n.clamp(-100, 0)); // -42 — batasi dalam rentang [-100, 0]
// Operasi bit
int a = 0b1010; // 10
int b = 0b1100; // 12
print(a & b); // 8 (0b1000) — bitwise AND
print(a | b); // 14 (0b1110) — bitwise OR
print(a ^ b); // 6 (0b0110) — bitwise XOR
print(a << 1); // 20 (0b10100) — shift kiri
print(a >> 1); // 5 (0b0101) — shift kanan
Parsing String ke int
#
// ANTI-PATTERN: mengasumsikan parsing selalu berhasil
int nilai = int.parse('abc'); // ✗ throws FormatException
// BENAR: gunakan tryParse untuk input yang tidak terpercaya
int? nilai = int.tryParse('42'); // 42
int? gagal = int.tryParse('abc'); // null — tidak throw
int? hex = int.tryParse('FF', radix: 16); // 255
// Tangani null dengan fallback
int tampil = int.tryParse(inputPengguna) ?? 0;
double — Bilangan Desimal
#
double menggunakan standar IEEE 754 64-bit floating-point — standar yang sama digunakan hampir semua bahasa modern. Ini berarti double memiliki presisi sekitar 15–17 digit desimal signifikan, tapi juga memiliki keterbatasan representasi yang harus dipahami.
double pi = 3.14159265358979;
double suhu = -273.15; // nol absolut
double ilmiah = 1.5e10; // 15.000.000.000 — notasi ilmiah
double kecil = 2.5e-4; // 0.00025
// Nilai spesial
print(double.infinity); // Infinity
print(double.negativeInfinity); // -Infinity
print(double.nan); // NaN (Not a Number)
print(double.maxFinite); // ~1.8e308
print(double.minPositive); // ~5e-324
Jebakan Floating-Point #
Ini adalah salah satu sumber bug paling umum di semua bahasa yang menggunakan IEEE 754:
// ANTI-PATTERN: membandingkan double dengan == secara langsung
double a = 0.1 + 0.2;
print(a == 0.3); // false — floating-point tidak presisi
print(a); // 0.30000000000000004
// BENAR: gunakan batas toleransi (epsilon) untuk perbandingan
const double epsilon = 1e-10;
bool hampirSama = (a - 0.3).abs() < epsilon;
print(hampirSama); // true
// ANTI-PATTERN: menyimpan nilai uang sebagai double
double harga = 19999.99;
double qty = 3;
double total = harga * qty;
print(total); // 59999.970000000005 — presisi error
// BENAR: gunakan int (dalam sen/rupiah terkecil) atau package 'decimal'
int hargaSen = 1999999; // Rp 19.999,99 dalam sen
int qtySatuan = 3;
int totalSen = hargaSen * qtySatuan;
print('Rp ${(totalSen / 100).toStringAsFixed(2)}'); // Rp 59999.97
Method dan Properti Penting #
double x = -3.7;
print(x.abs()); // 3.7
print(x.ceil()); // -3 — pembulatan ke atas
print(x.floor()); // -4 — pembulatan ke bawah
print(x.round()); // -4 — pembulatan ke terdekat
print(x.truncate()); // -3 — buang bagian desimal
print(x.toInt()); // -3 — konversi ke int (truncate)
// Format tampilan
print(x.toStringAsFixed(2)); // '-3.70' — 2 angka desimal
print(x.toStringAsPrecision(4)); // '-3.700' — 4 digit signifikan
print(x.toStringAsExponential(2)); // '-3.70e+0'
// Pengecekan khusus
double y = 0.0 / 0.0; // NaN
print(y.isNaN); // true
print(y.isFinite); // false
print(y.isInfinite); // false
num — Supertype Numerik
#
num adalah kelas abstrak induk dari int dan double. Gunakan num ketika sebuah fungsi atau variabel perlu menerima keduanya tanpa membatasi ke salah satu:
// Fungsi yang menerima int maupun double
double hitungAkar(num nilai) {
if (nilai < 0) throw ArgumentError('Nilai tidak boleh negatif');
return nilai.toDouble().sqrt(); // tidak ada sqrt() di num, konversi dulu
}
num a = 10; // int di balik layar
num b = 10.5; // double di balik layar
print(a.runtimeType); // int
print(b.runtimeType); // double
// ANTI-PATTERN: menggunakan num ketika tipe spesifik sudah pasti
num umur = 25; // ✗ umur pasti int, deklarasikan sebagai int
num pi = 3.14159; // ✗ pi pasti double, deklarasikan sebagai double
// BENAR: num hanya untuk kasus yang benar-benar generik
num hitungNilai(bool gunakanDesimal) {
return gunakanDesimal ? 3.14 : 3; // return tipe berbeda tergantung kondisi
}
bool — Nilai Boolean
#
bool di Dart hanya bisa bernilai true atau false — tidak ada truthy/falsy seperti di JavaScript atau Python. Semua ekspresi kondisional harus secara eksplisit menghasilkan bool.
bool aktif = true;
bool selesai = false;
// Operator logika
bool a = true;
bool b = false;
print(a && b); // false — AND
print(a || b); // true — OR
print(!a); // false — NOT
print(a ^ b); // true — XOR (exclusive or)
// ANTI-PATTERN: menggunakan nilai non-bool sebagai kondisi (tidak bisa di Dart)
int stok = 5;
if (stok) { ... } // ✗ error kompilasi — int bukan bool
if ('teks') { ... } // ✗ error kompilasi — String bukan bool
// BENAR: ekspresi kondisi harus menghasilkan bool secara eksplisit
if (stok > 0) { ... } // ✓
if (nama.isNotEmpty) { ... } // ✓
Short-Circuit Evaluation #
Dart mengevaluasi && dan || secara short-circuit — operand kanan hanya dievaluasi jika benar-benar diperlukan:
// Aman karena objek?.method() hanya dipanggil jika kondisi kiri true
String? nama;
if (nama != null && nama.isNotEmpty) {
// Jika nama null, ekspresi kanan tidak dievaluasi
print(nama.toUpperCase());
}
// Berguna untuk default value dengan efek samping
bool? izinGPS;
bool gunakanGPS = izinGPS ?? false; // false jika null
String — Teks
#
String di Dart adalah urutan karakter Unicode yang immutable. Setiap operasi “modifikasi” pada String (seperti + atau replaceAll) selalu menghasilkan objek String baru — objek aslinya tidak berubah.
String salam = 'Halo, Dart!';
String multiline = '''
Baris pertama
Baris kedua
Baris ketiga
''';
// Tanda kutip tunggal dan ganda equivalen
String a = 'Dart';
String b = "Dart";
print(a == b); // true
String Interpolation #
Interpolasi adalah cara idiomatis Dart untuk menyisipkan nilai ke dalam String:
String nama = 'Budi';
int umur = 25;
double gaji = 8_500_000;
// Interpolasi sederhana — $variabel
print('Nama: $nama');
// Interpolasi ekspresi — ${ekspresi}
print('Umur tahun depan: ${umur + 1}');
print('Gaji diformat: Rp ${gaji.toStringAsFixed(0)}');
print('Panjang nama: ${nama.length} karakter');
// Akses properti — tidak butuh {}
print('Huruf kapital: ${nama.toUpperCase()}');
// ANTI-PATTERN: konkatenasi string dengan + dalam loop
String hasil = '';
List<String> kata = ['satu', 'dua', 'tiga', 'empat', 'lima'];
for (final k in kata) {
hasil += k + ' '; // ✗ membuat objek String baru di setiap iterasi — O(n²)
}
// BENAR: gunakan StringBuffer untuk konkatenasi dalam loop
final buffer = StringBuffer();
for (final k in kata) {
buffer.write(k);
buffer.write(' ');
}
String hasil = buffer.toString(); // ✓ O(n)
// Atau gunakan join untuk kasus sederhana
String hasil = kata.join(' '); // ✓ paling ringkas
Method String yang Paling Sering Dipakai #
String teks = ' Halo, Dart! ';
// Pengecekan
print(teks.isEmpty); // false
print(teks.isNotEmpty); // true
print(teks.contains('Dart')); // true
print(teks.startsWith(' Halo')); // true
print(teks.endsWith('! ')); // true
// Transformasi
print(teks.trim()); // 'Halo, Dart!'
print(teks.trimLeft()); // 'Halo, Dart! '
print(teks.toUpperCase()); // ' HALO, DART! '
print(teks.toLowerCase()); // ' halo, dart! '
print(teks.replaceAll(',', ';')); // ' Halo; Dart! '
// Ekstraksi
print(teks.trim().substring(5)); // 'Dart!'
print(teks.trim().split(', ')); // ['Halo', 'Dart!']
print(teks.trim()[0]); // 'H' — akses karakter per indeks
// Pencarian
print(teks.indexOf('Dart')); // 7
print(teks.trim().length); // 11
// Padding
print('42'.padLeft(5)); // ' 42'
print('42'.padLeft(5, '0')); // '00042'
print('hi'.padRight(6, '.')); // 'hi....'
Raw String #
Raw string mengabaikan escape sequence — berguna untuk regex, path Windows, dan template string:
// String biasa — backslash diinterpretasi sebagai escape
String path = 'C:\\Users\\Budi\\Documents'; // perlu escape \\
// Raw string — backslash dibaca literal
String rawPath = r'C:\Users\Budi\Documents'; // tidak perlu escape
// Sangat berguna untuk regex
RegExp emailRegex = RegExp(r'^[\w\.-]+@[\w\.-]+\.\w{2,}$');
List<T> — Koleksi Berurutan
#
List adalah koleksi elemen yang diakses melalui indeks integer berbasis nol. Generic parameter T memastikan semua elemen bertipe sama — compiler akan menolak elemen dengan tipe yang salah.
// Deklarasi
List<int> angka = [1, 2, 3, 4, 5];
List<String> nama = ['Budi', 'Siti', 'Andi'];
List<Map<String, dynamic>> produk = []; // list kosong
// Akses
print(angka[0]); // 1 — indeks pertama
print(angka.last); // 5
print(angka.first); // 1
print(angka.length); // 5
// Modifikasi
angka.add(6); // tambah di akhir
angka.insert(0, 0); // sisipkan di indeks 0
angka.remove(3); // hapus nilai 3
angka.removeAt(0); // hapus di indeks 0
angka.removeLast(); // hapus elemen terakhir
Method Fungsional List #
List<int> angka = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// map — transformasi setiap elemen
List<int> kuadrat = angka.map((n) => n * n).toList();
// [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
// where — filter elemen
List<int> genap = angka.where((n) => n % 2 == 0).toList();
// [2, 4, 6, 8, 10]
// reduce — agregasi menjadi satu nilai
int jumlah = angka.reduce((acc, n) => acc + n); // 55
int maks = angka.reduce((a, b) => a > b ? a : b); // 10
// fold — seperti reduce tapi dengan nilai awal
int jumlahFold = angka.fold(0, (acc, n) => acc + n); // 55
// any dan every — pengecekan kondisi
bool adaYangBesar = angka.any((n) => n > 8); // true
bool semuaPositif = angka.every((n) => n > 0); // true
// sort — pengurutan in-place
List<int> acak = [3, 1, 4, 1, 5, 9, 2, 6];
acak.sort(); // [1, 1, 2, 3, 4, 5, 6, 9]
acak.sort((a, b) => b.compareTo(a)); // descending: [9, 6, 5, 4, 3, 2, 1, 1]
// sublist — irisan
print(angka.sublist(2, 5)); // [3, 4, 5] — indeks 2 hingga 4
// spread operator — menggabungkan list
List<int> a = [1, 2, 3];
List<int> b = [4, 5, 6];
List<int> gabung = [...a, ...b]; // [1, 2, 3, 4, 5, 6]
// ANTI-PATTERN: mengakses indeks tanpa pengecekan panjang
List<String> hasil = ambilData();
print(hasil[0]); // ✗ crash jika list kosong — RangeError
// BENAR: periksa panjang atau gunakan firstOrNull
if (hasil.isNotEmpty) {
print(hasil.first); // ✓
}
// Atau gunakan firstOrNull dari package collection
print(hasil.firstOrNull); // null jika kosong, tidak crash
Set<T> — Koleksi Unik
#
Set menjamin setiap elemen hanya muncul sekali. Operasi penambahan elemen yang sudah ada diabaikan secara diam-diam. Set juga mendukung operasi himpunan matematika secara langsung.
Set<String> tag = {'dart', 'flutter', 'mobile'};
// Modifikasi
tag.add('web'); // ditambahkan
tag.add('dart'); // diabaikan — sudah ada
print(tag.length); // 4
// Pengecekan
print(tag.contains('flutter')); // true
// Operasi himpunan
Set<String> a = {'dart', 'flutter', 'mobile'};
Set<String> b = {'dart', 'web', 'desktop'};
print(a.intersection(b)); // {'dart'} — irisan
print(a.union(b)); // {'dart', 'flutter', 'mobile', 'web', 'desktop'} — gabungan
print(a.difference(b)); // {'flutter', 'mobile'} — selisih A-B
print(b.difference(a)); // {'web', 'desktop'} — selisih B-A
// ANTI-PATTERN: menggunakan List untuk mengecek keunikan — O(n) per operasi
List<String> dilihat = [];
for (String item in data) {
if (!dilihat.contains(item)) { // ✗ O(n) setiap iterasi — total O(n²)
dilihat.add(item);
prosesItem(item);
}
}
// BENAR: gunakan Set untuk lookup keunikan — O(1) per operasi
Set<String> dilihat = {};
for (String item in data) {
if (dilihat.add(item)) { // ✓ add() mengembalikan false jika sudah ada
prosesItem(item);
}
}
Map<K, V> — Pasangan Kunci-Nilai
#
Map menyimpan data sebagai pasangan kunci-nilai. Kunci harus unik; nilai boleh duplikat. Akses nilai melalui kunci berjalan O(1) rata-rata.
Map<String, int> skor = {
'Budi': 90,
'Siti': 85,
'Andi': 92,
};
// Akses
print(skor['Budi']); // 90
print(skor['Tono']); // null — kunci tidak ada, tidak crash
print(skor['Tono'] ?? 0); // 0 — dengan fallback
// Modifikasi
skor['Rini'] = 88; // tambah atau update
skor.remove('Andi'); // hapus entri
// Pengecekan
print(skor.containsKey('Budi')); // true
print(skor.containsValue(85)); // true
print(skor.length); // 3
// Iterasi
skor.forEach((nama, nilai) {
print('$nama: $nilai');
});
// Transformasi
Map<String, String> grade = skor.map(
(nama, nilai) => MapEntry(nama, nilai >= 90 ? 'A' : 'B'),
);
// {'Budi': 'A', 'Siti': 'B', 'Rini': 'B'}
// Akses keys, values, entries
print(skor.keys.toList()); // ['Budi', 'Siti', 'Rini']
print(skor.values.toList()); // [90, 85, 88]
Map untuk Data Dinamis dari JSON #
// Map<String, dynamic> adalah tipe standar untuk data JSON
Map<String, dynamic> pengguna = {
'id': 'U001',
'nama': 'Budi Santoso',
'umur': 25,
'aktif': true,
'alamat': {
'kota': 'Jakarta',
'kodePos': '10110',
},
};
// Akses nested map
String kota = (pengguna['alamat'] as Map<String, dynamic>)['kota'];
print(kota); // Jakarta
// ANTI-PATTERN: menggunakan Map<String, dynamic> sebagai model data permanen
// Map tidak punya type safety — setiap akses bisa salah tipe
String nama = pengguna['Nama'] as String; // ✗ typo 'Nama' bukan 'nama', crash runtime
// BENAR: buat model kelas dengan fromJson() untuk data yang sering digunakan
class Pengguna {
final String id;
final String nama;
final int umur;
const Pengguna({required this.id, required this.nama, required this.umur});
factory Pengguna.fromJson(Map<String, dynamic> json) {
return Pengguna(
id: json['id'] as String,
nama: json['nama'] as String,
umur: json['umur'] as int,
);
}
}
Tipe Khusus: dynamic, Object, dan Never
#
Ketiga tipe ini sering membingungkan karena ketiganya tampak “bisa menyimpan apa saja”. Perbedaannya fundamental:
// dynamic — type checking dinonaktifkan sepenuhnya
dynamic apa = 'teks';
apa = 42; // ✓ ganti tipe — tidak dicek compiler
apa.tidakAda(); // ✓ saat compile — ✗ crash saat runtime
// Object — semua tipe adalah turunan Object, tapi type-safe
Object sesuatu = 'teks';
sesuatu = 42; // ✓ ganti tipe — dicek compiler
// sesuatu.length; // ✗ error kompilasi — Object tidak punya .length
(sesuatu as String).length; // ✓ perlu cast eksplisit
// Object? — sama dengan Object tapi nullable
Object? mungkinNull = null; // ✓
// ANTI-PATTERN: menggunakan dynamic untuk "kemudahan"
dynamic data = ambilDariAPI();
print(data.nama); // ✗ tidak ada jaminan — crash jika API berubah struktur
// BENAR: gunakan tipe yang tepat atau cast dengan pemeriksaan
Map<String, dynamic> data = ambilDariAPI();
if (data['nama'] is String) {
print(data['nama'] as String); // ✓ type-safe
}
// Atau lebih baik, buat model
final pengguna = Pengguna.fromJson(data);
print(pengguna.nama); // ✓ compile-time safe
Never adalah tipe yang menunjukkan sebuah fungsi tidak pernah mengembalikan nilai normal — selalu throw atau berjalan selamanya:
// Never sebagai return type — fungsi yang selalu throw
Never lemparError(String pesan) {
throw ArgumentError(pesan);
}
// Berguna dalam switch exhaustive
String deskripsikan(Object value) {
if (value is int) return 'angka: $value';
if (value is String) return 'teks: $value';
// Compiler tahu kode ini tidak bisa dicapai jika semua tipe sudah ditangani
throw UnimplementedError('Tipe tidak dikenal: ${value.runtimeType}');
}
Generics dan Type Parameters #
Generics memungkinkan kelas dan fungsi bekerja dengan berbagai tipe tanpa mengorbankan type safety. List<T>, Set<T>, dan Map<K,V> adalah contoh generic yang sudah ada di core Dart.
// Fungsi generic — bekerja dengan tipe apapun
T pertama<T>(List<T> list) {
if (list.isEmpty) throw StateError('List kosong');
return list.first;
}
int angka = pertama([1, 2, 3]); // T disimpulkan sebagai int
String kata = pertama(['a', 'b']); // T disimpulkan sebagai String
// Kelas generic
class Pasangan<A, B> {
final A pertama;
final B kedua;
const Pasangan(this.pertama, this.kedua);
@override
String toString() => '($pertama, $kedua)';
}
final p = Pasangan('Budi', 25); // Pasangan<String, int>
final q = Pasangan(true, [1, 2, 3]); // Pasangan<bool, List<int>>
// Generic dengan batasan tipe (bounded generics)
T nilaiMaks<T extends Comparable<T>>(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
print(nilaiMaks(10, 20)); // 20
print(nilaiMaks('apple', 'zen')); // zen
Variance dan Wildcard #
// Covariance — List<int> bukan subtipe dari List<num> di Dart
List<int> angka = [1, 2, 3];
// List<num> campur = angka; // ✗ error — type-safe, mencegah bug tersembunyi
// Tapi bisa dikonversi eksplisit
List<num> campur = angka.cast<num>(); // ✓ cast eksplisit
// Contoh kenapa ini penting:
// Jika List<int> bisa diassign ke List<num>, kamu bisa menambahkan double ke list int
// campur.add(3.14); // ini akan korupsi list angka yang bertipe int!
Konversi Antar Tipe #
Dart tidak melakukan konversi tipe secara implisit — setiap konversi harus dilakukan secara eksplisit. Ini mencegah bug halus yang sering terjadi di JavaScript:
// int ↔ double
int i = 42;
double d = i.toDouble(); // ✓ 42.0
int kembali = d.toInt(); // ✓ 42 (truncate, bukan round)
int dibulatkan = d.round(); // ✓ untuk pembulatan
// num → int atau double
num n = 3.7;
int bawah = n.floor().toInt(); // 3
int atas = n.ceil().toInt(); // 4
// String → int / double
int dariString = int.parse('42');
double dariStringD = double.parse('3.14');
int? amanParse = int.tryParse('bukan angka'); // null, tidak throw
// int / double → String
String str = 42.toString();
String desimal = 3.14159.toStringAsFixed(2); // '3.14'
// bool → tidak ada konversi implisit dari/ke tipe lain
// bool harus selalu ekspresi boolean eksplisit
bool aktif = (stok > 0); // ✓ bukan: bool aktif = stok;
// ANTI-PATTERN: cast eksplisit tanpa pengecekan tipe
Object nilai = ambilData();
String teks = nilai as String; // ✗ crash dengan CastError jika bukan String
// BENAR: periksa tipe sebelum cast, atau gunakan is untuk type promotion
if (nilai is String) {
// Di sini Dart otomatis memperlakukan 'nilai' sebagai String
print(nilai.toUpperCase()); // ✓ tidak perlu cast
}
// Atau gunakan as dengan sadar bahwa bisa throw
try {
String teks = nilai as String;
print(teks);
} on TypeError {
print('Nilai bukan String: ${nilai.runtimeType}');
}
Ringkasan #
- Semua tipe adalah objek di Dart — tidak ada primitive type terpisah.
int,double,bool, danStringsemuanya kelas yang mewarisi dariObject.intuntuk bilangan bulat tanpa batas ukuran di Dart VM; gunakantryParse()bukanparse()untuk input yang tidak terpercaya.doublemenggunakan IEEE 754 — jangan gunakan==untuk membandingkan double, dan hindari menyimpan nilai uang sebagai double.numadalah supertypeintdandouble— gunakan hanya ketika fungsi memang perlu menerima keduanya.boolbersifat ketat — tidak ada truthy/falsy. Semua kondisi harus menghasilkanboolsecara eksplisit.Stringimmutable — setiap “modifikasi” menghasilkan objek baru. GunakanStringBufferuntuk konkatenasi dalam loop, danjoin()untuk menggabungkan list.List<T>untuk koleksi berurutan dengan akses cepat via indeks;Set<T>untuk koleksi unik dengan lookup O(1);Map<K,V>untuk pasangan kunci-nilai dengan akses O(1).- Hindari
Map<String, dynamic>sebagai model data permanen — buat kelas denganfromJson()untuk type safety yang sesungguhnya.dynamicmenonaktifkan type checker — semua error dipindahkan dari compile time ke runtime. Gunakan hanya sebagai jalan terakhir.- Generics memungkinkan kode reusable yang tetap type-safe —
List<T>,Set<T>,Map<K,V>adalah contoh sehari-hari, tapi kamu juga bisa membuat fungsi dan kelas generic sendiri.- Konversi tipe harus eksplisit — Dart tidak mengonversi int ke double secara otomatis. Gunakan
toDouble(),toInt(),toString(), danparse()secara sadar.