Mengenal Dart
Kenapa Dart, SDK, dartpad vs CLI.
Mengenal Dart
- 01Pahami konsepnya · 3
- 02Coba contoh kode · 1
Kenapa Dart, SDK, dartpad vs CLI.
Dart dikembangkan Google (2011) sebagai bahasa client-optimized untuk aplikasi multi-platform. Hari ini ia paling dikenal sebagai bahasa di balik Flutter — framework UI yang membangun aplikasi native iOS, Android, web, dan desktop dari satu codebase. Dart dikompilasi ke native code (cepat) atau JavaScript (untuk web).
Dart sengaja didesain "membosankan" — mudah dipelajari, sintaks C-style yang familiar, dan tooling yang matang. Fitur modern: null safety (wajib sejak Dart 2.12), pattern matching (Dart 3), records, extension types, dan class modifiers.
Untuk memasang: unduh Dart SDK dari dart.dev/get-dart (bundling Flutter otomatis menyertakan Dart). Editor: VS Code dengan ekstensi Dart, atau Android Studio/IntelliJ. DartPad (dartpad.dev) adalah playground online tanpa instalasi. Jalankan dengan `dart run main.dart` di terminal.
Perintah ini memastikan Dart SDK terpasang. Dart 3.x adalah versi saat ini dengan null safety, records, dan pattern matching.
main(), print(), komentar.
Program Dart dimulai dari fungsi main — sama seperti C, Java, dan Go. void main() { print("Halo"); } mencetak satu baris. Setiap pernyataan diakhiri titik koma. Kurung kurawal untuk blok.
Komentar: // satu baris, /// doc comment (untuk dokumentasi API, dibaca oleh dart doc), /* */ blok. Doc comment di atas fungsi/class adalah standar dokumentasi Dart.
String interpolation: "Halo $nama" untuk variabel sederhana, "${ekspresi}" untuk ekspresi. Ini cara utama merangkai teks di Dart — lebih bersih daripada + berulang.
String interpolation $nama menyisipkan variabel langsung. ${umur + 1} menghitung ekspresi di dalam kurung kurawal. /// doc comment muncul di dart doc dan IDE tooltip.
Uji: cetak `Halo, Dart!` lalu `Siap belajar!`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
void main() {
print("Halo, Dart!");
print("Nanti belajar");
}void main() {
// komentar satu baris
print("Halo dari Dart!");
/// doc comment — untuk dokumentasi API
String nama = "Ari";
int umur = 17;
print("$nama berumur ${umur + 1} tahun depan");
}var, final, const, type inference, dynamic.
Dart punya tiga kata kunci deklarasi: var (tipe disimpulkan, bisa re-assign), final (diisi sekali, runtime), const (compile-time constant). var nama = "Ari" menghasilkan String yang tidak bisa diubah tipenya tapi bisa re-assign. final berhenti setelah diisi. const harus bisa dihitung saat kompilasi.
Tipe eksplisit: String nama = "Ari"; int umur = 17; — selalu opsional karena var menebak. dynamic adalah tipe yang BISA berubah tipe — hindari kecuali benar-benar perlu (JSON decode).
Perbedaan final vs const: final DateTime now = DateTime.now() sah (runtime); const DateTime now = DateTime.now() ERROR (bukan compile-time). const untuk nilai literal: const pi = 3.14.
var untuk nilai yang berubah, final untuk nilai sekali isi, const untuk literal compile-time. DateTime.now() hanya final karena nilainya baru diketahui saat runtime.
Uji: final nama = "Ari"; int umur = 17; cetak `Ari berumur 18 tahun depan` (umur+1).
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
void main() {
final nama = "Ari";
int umur = 17;
print("$nama $umur");
}void main() {
var nama = "Ari"; // String, bisa re-assign
final umur = 17; // int, tidak bisa re-assign
const pi = 3.14159; // compile-time constant
nama = "Budi"; // OK — var re-assignable
// umur = 18; // ERROR — final
final sekarang = DateTime.now(); // OK — runtime
// const sekarang2 = DateTime.now(); // ERROR — bukan compile-time
print("$nama $umur $pi");
print(sekarang);
}int, double, String, bool, num, dynamic, konversi.
Dart punya tujuh tipe built-in: int (64-bit), double (IEEE 754), String, bool, num (induk int+double), List, Map, Set. Semuanya objek — bahkan int punya method .toString(), .abs(). dynamic menonaktifkan type checking untuk variabel itu.
Konversi: int.parse("42"), double.parse("3.14"), 42.toString(), 3.14.toInt() (memotong). String→int gagal melempar FormatException. num.toInt() memotong desimal (3.9 → 3).
num adalah tipe induk yang menerima int atau double. toInt() memotong (bukan membulatkan). runtimeType menunjukkan tipe aktual saat runtime.
Uji: int.parse("42") + 8 — harus `50`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
void main() {
print("42");
}void main() {
int umur = 17;
double tinggi = 168.5;
String nama = "Ari";
bool aktif = true;
num berapa = 42; // menerima int ATAU double
print("$nama $umur $tinggi $aktif");
print(berapa);
print(int.parse("42") + 8);
print(3.9.toInt()); // 3 (dipotong)
print("42".runtimeType); // String
}Aritmetika, perbandingan, type test, cascade `..`.
Operator aritmetika: + - * / % ~/(integer division). Perbandingan: == != < > <= >=. Logika: && || !. Type test: is, is!, as (cast).
Cascade (..) memungkinkan memanggil beberapa method pada objek yang sama tanpa menulis namanya berulang: canvas..rect(..)..fill(..). Ini idiom Dart yang bersih untuk builder pattern.
Null-aware: ?? (nullish coalescing), ?. (conditional access), ??= (assign if null), ! (null assertion).
~/ adalah integer division khas Dart (7 ~/ 2 = 3). ?? memberi cadangan untuk null. ??= hanya mengisi bila masih null.
Uji: 7 ~/ 2 dan 7 % 2 — harus `3` dan `1`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
void main() {
print(7 / 2);
print(7 % 2);
}Object bacaNilai() => "Ari";
void main() {
print(7 / 2); // 3.5
print(7 ~/ 2); // 3 — integer division
print(7 % 2); // 1
Object nama = bacaNilai();
print(nama is String); // true
print(nama is! int); // true
int? mungkin = null;
print(mungkin ?? 0); // 0
mungkin ??= 10; // assign jika null
print(mungkin); // 10
}Interpolasi, triple-quote, method umum, raw string.
String interpolation adalah fitur bintang Dart: "Halo $nama" untuk variabel, "${ekspresi}" untuk ekspresi — lebih bersih daripada +. Triple-quote '''...''' untuk multi-baris. Raw string r"..." mematikan escape.
Method penting: .length, .toUpperCase(), .toLowerCase(), .trim(), .split(","), .contains(), .replaceAll(), .substring(0,3), .padLeft(3, "0"). Semua menghasilkan String baru (immutable).
Interpolasi ${umur + 1} menghitung ekspresi. padLeft mengisi nol di depan untuk format angka. Raw string r"..." penting untuk path Windows dan regex.
Uji: "halo dunia".toUpperCase().split(" ") — harus `[HALO, DUNIA]`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
void main() {
print("halo dunia");
}void main() {
String nama = "Ari";
int umur = 17;
print("$nama berumur ${umur + 1} tahun depan");
String multi = """
Baris pertama
Baris kedua""";
print(multi);
print("halo".toUpperCase());
print(" spasi ".trim());
print("5".padLeft(3, "0")); // 005
print(r"C:\folder"); // raw — backslash tetap
}Alias tipe untuk fungsi dan koleksi kompleks.
typedef memberi nama pendek untuk tipe kompleks: typedef IntList = List<int>; mengubah List<int> menjadi IntList yang lebih terbaca. Untuk fungsi: typedef Calculator = int Function(int, int); — signature fungsi jadi tipe first-class.
Symbol (#nama) adalah identifier yang di-minify saat build — jarang ditulis langsung, tapi muncul di reflection (#foo sebagai Mirror). Praktisnya: typedef jauh lebih bergunakan daripada Symbol untuk kode sehari-hari.
typedef Operasi mendefinisikan signature fungsi (int, int) → int. Variabel hitung bisa menampung fungsi mana pun yang cocok — tambah atau kali.
Uji: typedef Kali; fungsi kuadrat(7) — harus `49`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
typedef Kali = int Function(int);
void main() {
// buat fungsi kuadrat di sini
print("buat di sini");
}typedef IntList = List<int>;
typedef Operasi = int Function(int, int);
int tambah(int a, int b) => a + b;
int kali(int a, int b) => a * b;
void main() {
IntList angka = [1, 2, 3];
print(angka);
Operasi hitung = tambah;
print(hitung(3, 5)); // 8
hitung = kali;
print(hitung(3, 5)); // 15
}BARU Dart 3: kumpulan nilai anonim, positional & named fields.
Record adalah fitur BARU Dart 3: kumpulan nilai tanpa perlu membuat class. Record positional: var titik = (3, 7); — akses titik.$1 dan titik.$2. Record named: var user = (nama: "Ari", umur: 17); — akses user.nama dan user.umur.
Record berguna untuk: fungsi yang mengembalikan beberapa nilai (return (min, max);), destructuring (var (x, y) = titik;), dan pola pattern matching. Record bersifat value-based: dua record dengan isi sama dianggap ==.
Destructuring memecah record langsung ke variabel: var (a, b) = (10, 20); memberi a=10 dan b=20. Ini menggantikan tuple yang tidak ada di Dart sebelum Dart 3.
Record positional diakses via $1, $2; named via nama properti. Destructuring var (x, y) = ... memecah langsung. Fungsi rentang mengembalikan (int, int) sebagai record — tanpa perlu class.
Uji: record (10, 20) → destructure a, b → a+b harus `30`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
void main() {
var r = (10, 20);
print(r.$1);
}void main() {
// positional
var titik = (3, 7);
print(titik.$1 + titik.$2); // 10
// named
var user = (nama: "Ari", umur: 17);
print("${user.nama} (${user.umur})");
// destructuring
var (x, y) = (10, 20);
print("x=$x y=$y");
// multi-return
var (min, maks) = rentang([3, 8, 1, 9, 4]);
print("$min..$maks");
}
(int, int) rentang(List<int> d) {
return (d.reduce((a, b) => a < b ? a : b),
d.reduce((a, b) => a > b ? a : b));
}if/else, switch expression Dart 3.
if/else seperti bahasa lain. Switch Dart 3 menjadi EXPRESSION — mengembalikan nilai: var label = switch(nilai) { >= 90 => "A", >= 80 => "B", _ => "C" };. Tidak ada fallthrough.
Guard clause: if (nilai < 0 || nilai > 100) return "invalid"; — keluar dulu bila masukan tidak sah.
Switch expression Dart 3: tiap case mengembalikan nilai setelah =>. _ adalah default. Hasilnya langsung ditugaskan ke variabel predikat.
Uji: nilai 82 → `B`; ganti ke 60 → `D`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
void main() {
int nilai = 82;
print("?");
}void main() {
int nilai = 82;
String predikat = switch (nilai) {
>= 90 => "A",
>= 80 => "B",
>= 70 => "C",
_ => "D",
};
print(predikat);
}BARU Dart 3: literal, variable, list, map, record, logical, relational.
Pattern matching adalah fitur terbesar Dart 3 — mengganti if/else berantai dengan pola deklaratif. Pola literal: case 200:; pola variable: case int n:; pola record: case (int a, int b):; pola list: case [first, ...rest]:. Logical OR: case 200 || 201 || 202:. Guard: case int n when n > 100:.
Destructuring dalam pola: switch (poin) { [int pertama, int kedua] => ... } memecah list langsung ke variabel. Record pola: case (nama: String n, umur: int u) memberi n dan u dari named record.
Switch expression dengan pola: int n when n > 100 adalah guard; [int a, int b] mendestruksi list; {"nama": String n} mencocokkan map dengan kunci nama. _ menampung sisanya.
Uji: jelaskan([10, 20]) dengan pola list → "pasangan: 10+20=30".
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
String jelaskan(Object obj) => switch (obj) {
_ => "tidak dikenal",
};
void main() => print(jelaskan([10, 20]));String jelaskan(Object obj) => switch (obj) {
0 => "nol",
int n when n > 100 => "angka besar: $n",
int n => "angka: $n",
String s => "teks: $s",
[int a, int b] => "pasangan: $a+$b=${a + b}",
{"nama": String n} => "objek bernama $n",
_ => "tidak dikenal",
};
void main() {
print(jelaskan(0));
print(jelaskan(42));
print(jelaskan("halo"));
print(jelaskan([3, 5]));
print(jelaskan({"nama": "Ari"}));
}for, while, for-in, break/continue.
Dart punya for klasik (for var i = 0; ...; i++), for-in untuk iterable (for (var x in list)), while, do-while. break dan continue bekerja seperti biasa.
for-in adalah bentuk yang paling sering dipakai untuk List/Set/Map. Untuk Map: for (var key in map.keys) atau map.forEach((k, v) { ... }).
for-in menyusuri List langsung memberi elemen. forEach pada Map memberi kunci dan nilai sekaligus dalam callback.
Uji: jumlahkan 1-100 dengan for — harus `5050`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
void main() {
int total = 0;
print(total);
}void main() {
for (var i = 1; i <= 5; i++) print(i);
var buah = ["apel", "mangga", "jeruk"];
for (var b in buah) print(b);
var stok = {"kopi": 12, "teh": 8};
stok.forEach((k, v) => print("$k: $v"));
}try/on/catch/finally, throw, Error vs Exception.
Dart punya try/catch/finally seperti biasa, plus on untuk tipe spesifik: try { } on FormatException catch (e) { } catch (e) { }. throw melempar; rethrow meneruskan.
Error vs Exception: Error untuk bug programmer (RangeError, TypeError) — tidak seharusnya ditangkap. Exception untuk kondisi runtime yang wajar (FormatException, IOException) — layak ditangani.
on Exception catch (e) menangkap tipe spesifik. Interpolasi $e memanggil toString() untuk menampilkan detail exception; Exception umum tidak punya properti message. finally selalu berjalan.
Uji: throw Exception("test") → catch → cetak `test`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
void main() {
try {
// throw di sini
} catch (e) {
print(e);
}
}int bagi(int a, int b) {
if (b == 0) throw Exception("pembagi nol");
return a ~/ b;
}
void main() {
try {
print(bagi(10, 2));
print(bagi(10, 0));
} on Exception catch (e) {
print("Gagal: $e");
} finally {
print("Selesai.");
}
}assert() untuk debug, @override, @deprecated.
assert(kondisi, pesan) memeriksa kondisi saat development — hanya aktif di mode debug (dart run dan flutter run). Di production (dart compile) otomatis dinonaktifkan.
Metadata (@annotation) memberi informasi tambahan untuk compiler dan alat: @override menandai method yang menimpa induk (compiler memeriksa benar/salahnya); @deprecated menandai API usang; @JS untuk interop JavaScript.
@override memaksa compiler memverifikasi bahwa suara() memang ada di Hewan. assert hanya berjalan di mode debug.
Uji: assert(1+1 == 2) — program berjalan, cetak `ok`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
void main() {
print("tulis assert di sini");
}class Hewan {
void suara() => print("...");
}
class Kucing extends Hewan {
@override
void suara() => print("meong");
}
void main() {
assert(1 + 1 == 2, "matematika rusak");
Kucing().suara();
}Named params {}, optional [], default, arrow =>.
Parameter named memakai kurung kurawal: void sapa({String nama = "Tamu", int umur = 0}) — dipanggil dengan nama: sapa(nama: "Ari"). Optional positional memakai kurung siku: void info(String nama, [String? kota]). Arrow => untuk fungsi satu ekspresi: int kali(int a, int b) => a * b.
Named parameter WAJIB dengan required: void daftar({required String nama}) — pemanggil tidak bisa melewatkan.
Named parameter dipanggil dengan label: nama: "Ari". Default value = "Tamu" aktif bila tidak dikirim.
Uji: int kuadrat(int n) => n*n — kuadrat(7) harus `49`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
void main() {
print("buat fungsi di sini");
}int tambah(int a, int b) => a + b;
void sapa({String nama = "Tamu", int umur = 0}) => print("$nama ($umur)");
void main() {
print(tambah(3, 5));
sapa(nama: "Ari", umur: 17);
sapa(nama: "Budi");
sapa();
}Anonymous, closure, fungsi sebagai argumen.
Fungsi anonymous: (a, b) => a + b — bisa disimpan ke variabel atau dikirim sebagai argumen. Closure: fungsi yang menangkap variabel dari scope luar. Higher-order: fungsi yang menerima/mengembalikan fungsi.
hitung tertutup dalam closure — hanya fungsi next yang bisa mengaksesnya. Anonymous (int n) => n * n disimpan ke variabel kuadrat.
Uji: closure mulai 10 — next() harus `11`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
Function buat() {
int n = 10;
return () => n;
}
void main() {
var next = buat();
print(next());
}Function buatPenghitung() {
int hitung = 0;
return () => ++hitung;
}
void main() {
var next = buatPenghitung();
print(next()); // 1
print(next()); // 2
print(next()); // 3
var kuadrat = (int n) => n * n;
print(kuadrat(7)); // 49
}Grow/list, methods, spread, collection-if/for.
List Dart fleksibel: List<int> angka = [1,2,3]. Methods: add, addAll, removeAt, insert, sort, reversed, contains, indexOf, sublist, join. Spread ... membongkar list ke list lain. Collection-if: [if (kondisi) nilai] menyertakan kondisional. Collection-for: [for (var i in range) i * 2].
Spread ...a menyisipkan seluruh isi list a ke list b. Collection-if menyaring langsung di dalam literal — sangat idiomatik Dart.
Uji: [5,3,8,1,9].sort() — harus `[1, 3, 5, 8, 9]`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
void main() {
var d = [5, 3, 8, 1, 9];
print(d);
}void main() {
var a = [1, 2, 3];
var b = [0, ...a, 4]; // spread
print(b); // [0, 1, 2, 3, 4]
var genap = [for (var i = 0; i <= 10; i++) if (i % 2 == 0) i];
print(genap); // [0, 2, 4, 6, 8, 10]
var ada = a.contains(2);
print(ada);
}Koleksi unik, union/intersection/difference.
Set otomatis menghilangkan duplikat: {1,2,2,3} = {1,2,3}. Operasi himpunan: union | , intersection &, difference -. set.contains() sangat cepat.
union menggabung semua, intersection ambil yang sama, difference ambil yang di a saja. Duplikat otomatis hilang.
Uji: {1,2,2,3,3,3}.length — harus `3`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
void main() {
print({1, 2, 2, 3, 3, 3}.length);
}void main() {
var a = {1, 2, 3, 4, 5};
var b = {4, 5, 6, 7, 8};
print(a.union(b));
print(a.intersection(b));
print(a.difference(b));
print([1, 2, 2, 3].toSet().length); // 3
}Key-value pair, CRUD, iteration, cascade.
Map menyimpan pasangan kunci-nilai: {"kopi": 12}. CRUD: m["susu"] = 5 (tambah/ubah), m.remove("teh"), m.containsKey. Iteration: forEach, for-in keys/values.
m["kunci"] mengakses/menambah/mengubah. ! pada stok["teh"]! menegaskan non-null (mengembalikan int? secara default).
Uji: Map {"a":10,"b":20} → jumlahkan values → `30`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
void main() {
var m = {"a": 10, "b": 20};
print(m);
}void main() {
var stok = {"kopi": 12, "teh": 8};
stok["susu"] = 5;
stok["teh"] = stok["teh"]! + 2;
stok.forEach((k, v) => print("$k: $v"));
print(stok.containsKey("matcha"));
print(stok.length);
}List<T>, Map<K,V>, method generics, bounded.
Generics memastikan type safety: List<String> hanya menerima String. Method generics: T pertama<T>(List<T> list). Bounded: T extends num membatasi ke num atau turunannya.
pertama<T> menerima List<int> ATAU List<String> — T menyesuaikan tipenya. Bounded T extends num membatasi ke angka.
Uji: terbesar<T extends num>([3,8,1,9,4]) — harus `9`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
// buat fungsi generik terbesar<T extends num> di sini
void main() {
print(terbesar([3, 8, 1, 9, 4]));
}
// hint: T terbesar<T extends num>(List<T> data) { ... }T pertama<T>(List<T> list) => list.first;
double rata<T extends num>(List<T> d) => d.reduce((a, b) => a + b as T) / d.length;
void main() {
print(pertama([1, 2, 3])); // 1 (int)
print(pertama(["a", "b"])); // a (String)
}Class, named/default/redirecting constructor, initializer.
Class Dart: class Siswa { String nama; int poin; Siswa(this.nama, this.poin); }. Constructor bisa bernama: Siswa.kosong() : nama = "", poin = 0;. Redirecting: Siswa.dariJson(Map m) : this(m["nama"], m["poin"]);. Initializer list: Siswa() : poin = 0, assert(nama.isNotEmpty);
this.nama mengisi field langsung di parameter constructor. Named constructor .kosong() dan .istimewa() membuat instance dengan cara berbeda.
Uji: Siswa("Ari",17).info() harus `Ari (17)`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
class Siswa {
String nama;
int poin;
Siswa(this.nama, this.poin);
String info() => "?";
}
void main() => print(Siswa("Ari", 17).info());class Siswa {
String nama;
int poin;
Siswa(this.nama, this.poin);
Siswa.kosong() : nama = "?", poin = 0;
Siswa.istimewa(String n) : this(n, 100);
}
void main() {
var a = Siswa("Ari", 17);
var b = Siswa.kosong();
var c = Siswa.istimewa("Budi");
print("${a.nama} ${b.nama} ${c.nama}");
}Instance method, getter/setter, static, operator override.
Getter: int get total => poin * 10; dipanggil tanpa kurung. Setter: set poinBaru(int v) => poin = v;. Static method milik class: static int hitung = 0;. Operator override: bool operator ==(Object lain) => nama == (lain as Siswa).nama;
Getter fahrenheit MENGHITUNG dari _celsius — tidak menyimpan nilai sendiri. Setter menerima nilai lalu mengubah _celsius.
Uji: getter status → poin >= 70 harus `lulus`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
class Siswa {
int poin = 75;
// buat getter status di sini
}
void main() {
print("buat getter di sini");
}class Suhu {
double _celsius = 0;
double get celsius => _celsius;
set celsius(double v) => _celsius = v;
double get fahrenheit => _celsius * 9 / 5 + 32;
set fahrenheit(double f) => _celsius = (f - 32) * 5 / 9;
}
void main() {
var s = Suhu();
s.celsius = 25;
print(s.fahrenheit); // 77
}?, !, ??, ?., late — wajib sejak Dart 2.12.
Dart null safety: tipe biasa TIDAK BISA null. String nama = null ERROR. String? nama BOLEH null. Operator: ?. (akses aman), ?? (cadangan), ! (assert non-null), ??= (assign if null).
late menunda inisialisasi: late String deskripsi; — diisi nanti tapi dijamin sebelum dibaca. Berguna untuk dependency injection dan field yang dihitung setelah construction.
?. mengembalikan null alih-alih error bila objek null. ! memaksa non-null (error bila ternyata null). late menunda wajib isi.
Uji: String? nama = null; print(nama ?? "Tamu") — harus `Tamu`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
void main() {
String? nama = null;
print(nama);
}int panjangWajib(String? teks) => teks!.length;
void tampilkan(String? nama) {
print(nama ?? "Tamu");
print(nama?.length);
}
void main() {
tampilkan(null); // Tamu, null
tampilkan("Ari"); // Ari, 3
print(panjangWajib("Ari")); // 3; ! menegaskan non-null
late String deskripsi;
deskripsi = "baru diisi";
print(deskripsi);
}extends, super, @override, abstract class.
extends mewarisi: class Admin extends Pengguna. super mengakses induk. @override menimpa method. Abstract class tidak bisa diinstansiasi — hanya untuk diwarisi: abstract class Bentuk { double luas(); }
Bentuk abstract memaksa Persegi mengimplementasikan luas(). @override menandai penimpaan.
Uji: Persegi(4).luas() harus `16`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
abstract class Bentuk {
double luas();
}
class Persegi extends Bentuk {
double sisi;
Persegi(this.sisi);
@override
double luas() => 0; // lengkapi
}
void main() => print(Persegi(4).luas());abstract class Bentuk {
double luas();
}
class Persegi extends Bentuk {
double sisi;
Persegi(this.sisi);
@override
double luas() => sisi * sisi;
}
void main() => print(Persegi(4).luas());implements (implicit interface), mixin with.
Setiap class Dart adalah implicit interface. class Kucing implements Hewan — harus mengimplementasikan SEMUA method Hewan. Mixin: mixin Loggable { void log() => print("log"); } lalu class Item with Loggable — menambahkan method tanpa pewarisan.
Mixin Loggable dan Greetable menambah method log() dan sapa() ke Item tanpa pewarisan — class Item bisa memakai banyak mixin sekaligus.
Uji: mixin dengan halo() → item.halo() harus `Halo!`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
// buat mixin Halo di sini
class Item /* with Halo */ {
}
void main() {
print("tulis mixin di sini");
}mixin Loggable {
void log(String msg) => print("[LOG] $msg");
}
mixin Greetable {
String get nama;
void sapa() => print("Halo $nama!");
}
class Item with Loggable, Greetable {
@override
String nama = "Widget";
}
void main() {
var item = Item();
item.log("dibuat");
item.sapa();
}Simple enum, enhanced enum (Dart 3), dot shorthand.
Simple enum: enum Warna { merah, hijau, biru }. Enhanced enum (Dart 3) boleh punya field, constructor, dan method: enum Level with Comparable { bronze(1), silver(2), gold(3); final int nilai; const Level(this.nilai); }. Dot shorthand: .merah menggantikan Warna.merah bila tipe diketahui.
Enhanced enum punya constructor const, field, dan toString override. Level.values memberi semua anggota.
Uji: Level.values.length — harus `3`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
enum Level { bronze, silver, gold }
void main() => print("?");enum Level {
bronze(1, "Bronze"),
silver(2, "Silver"),
gold(3, "Gold");
final int nilai;
final String label;
const Level(this.nilai, this.label);
@override
String toString() => label;
}
void main() {
print(Level.gold); // Gold
print(Level.gold.nilai); // 3
print(Level.values);
}Tambah method ke tipe existing tanpa subclass.
extension pada String: extension StringX on String { String get kapitalAwal => isNotEmpty ? this[0].toUpperCase() + substring(1) : this; } — semua String sekarang punya .kapitalAwal. Bisa juga generic: extension ListNum<T extends num> on List<T>.
Extension Kapital menambah .kapitalAwal dan .jumlahKata ke SEMUA String — tanpa mengubah class String sendiri.
Uji: extension pada int → .kuadrat — 7.kuadrat harus `49`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
// buat extension Kuadrat on int di sini
void main() {
print("buat extension di sini");
}extension Kapital on String {
String get kapitalAwal =>
isNotEmpty ? "${this[0].toUpperCase()}${substring(1)}" : this;
int get jumlahKata => trim().split(RegExp(r"s+")).length;
}
void main() {
print("halo dunia".kapitalAwal); // Halo dunia
print("satu dua tiga".jumlahKata); // 3
}BARU Dart 3.3: zero-cost wrapper dengan tipe baru.
Extension type membungkus tipe existing menjadi tipe BARU tanpa biaya runtime: extension type Email(String nilai) — sekarang Email dan String adalah tipe berbeda meski representasinya sama. Zero-cost: tidak ada objek wrapper di memory.
Email punya identitas tipe sendiri; getter teks membuka representasi String saat dibutuhkan. Extension type diperiksa sebagai tipe terpisah oleh compiler tanpa objek pembungkus tambahan saat runtime.
Uji: extension type dengan getter panjang — email.panjang harus `17`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
// buat extension type Email di sini
void main() {
print("buat extension type di sini");
}extension type Email(String _nilai) {
bool get valid => _nilai.contains("@");
String get teks => _nilai;
}
void main() {
var email = Email("ari@kampus.com");
print(email.valid); // true
print(email.teks); // ari@kampus.com
}BARU Dart 3: abstract, base, final, sealed, interface, mixin class.
Class modifiers mengontrol bagaimana class digunakan: abstract (tak bisa instansiasi), base (harus di-extends, tak bisa implements), final (hanya bisa extends di library yang sama), interface (hanya bisa implements), sealed (subkelas tertutup untuk switch exhaustiveness), mixin class (bisa dipakai sebagai mixin DAN class).
sealed Bentuk membatasi subkelas hanya ke Persegi dan Lingkaran — switch menjadi exhaustive (compiler memastikan semua kasus tercakup).
Uji: sealed class + switch — luas(Persegi(4)) harus `16`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
sealed class Bentuk {}
class Persegi extends Bentuk { final double sisi; Persegi(this.sisi); }
class Lingkaran extends Bentuk { final double r; Lingkaran(this.r); }
double luas(Bentuk b) => switch (b) {
// lengkapi pola di sini
_ => 0,
};
void main() => print(luas(Persegi(4)));sealed class Bentuk {}
class Persegi extends Bentuk { final double sisi; Persegi(this.sisi); }
class Lingkaran extends Bentuk { final double r; Lingkaran(this.r); }
double luas(Bentuk b) => switch (b) {
Persegi(sisi: var s) => s * s,
Lingkaran(r: var r) => 3.14159 * r * r,
};
void main() {
print(luas(Persegi(4)));
print(luas(Lingkaran(7)));
}Class sebagai fungsi via method call().
Class yang punya method call() bisa dipanggil seperti fungsi: class Kali { int call(int a, int b) => a * b; } lalu var k = Kali(); k(3, 5) menghasilkan 15. Ini menggantikan fungsi murni untuk kasus yang butuh state.
diskon20(100000) memanggil call() — objek berperilaku persis seperti fungsi. persen tersimpan sebagai state.
Uji: callable Diskon(50) → d(200) harus `100`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
class Diskon {
double persen;
Diskon(this.persen);
// buat method call di sini
}
void main() {
var d = Diskon(50);
print("lengkapi call()");
}class Diskon {
double persen;
Diskon(this.persen);
double call(double harga) => harga * (1 - persen / 100);
}
void main() {
var diskon20 = Diskon(20);
print(diskon20(100000)); // 80000
}Future, async/await, then/catchError, Future.wait.
Future<T> adalah janji nilai di masa depan: Future<int> menghasilkan int nanti. async menandai fungsi asinkron; await menjeda sampai Future selesai. Future.delayed(Duration(seconds:1), () => "hasil") mensimulasikan tugas lambat.
Future.wait([f1, f2]) menunggu semua paralel. .then(hasil) .catchError(e) adalah alternatif chaining tanpa await.
await menjeda sampai Future selesai. Future.wait menjalankan dua Future paralel lalu menunggu hasilnya sekaligus.
Uji: async dengan Future.value(42) — await hasilnya harus `42`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
Future<void> main() async {
// await Future.value(42) di sini
print("lengkapi");
}Future<String> ambilData() async {
await Future.delayed(Duration(milliseconds: 100));
return "data tiba";
}
Future<void> main() async {
print("mulai");
var hasil = await ambilData();
print(hasil);
var paralel = await Future.wait([
Future.value(10),
Future.value(20),
]);
print(paralel.reduce((a, b) => a + b)); // 30
}Stream, async*, yield, await for, listen.
Stream adalah urutan nilai async — seperti Future tapi banyak nilai dari waktu ke waktu. Generator: Stream<int> hitung(int n) async* { for (var i = 1; i <= n; i++) yield i; }. Konsumsi: await for (var x in stream) atau stream.listen((x) { ... }).
async* + yield menghasilkan nilai satu per satu. await for menyusuri stream sampai selesai — tiap yield memicu satu iterasi.
Uji: stream hitung(3) → jumlahkan — harus `6`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
Stream<int> hitung(int n) async* {
for (var i = 1; i <= n; i++) yield i;
}
Future<void> main() async {
int total = 0;
// await for di sini
print(total);
}Stream<int> hitung(int n) async* {
for (var i = 1; i <= n; i++) {
await Future.delayed(Duration(milliseconds: 10));
yield i;
}
}
Future<void> main() async {
await for (var x in hitung(5)) {
print(x);
}
}Isolate.spawn, SendPort/ReceivePort, message passing.
Dart single-threaded per isolate. Isolate.spawn menjalankan fungsi di thread terpisah tanpa shared memory — komunikasi lewat pesan (SendPort/ReceivePort). Isolate utama: Isolate.main; worker: Isolate.run untuk komputasi berat tanpa membekukan UI.
Isolate.run menjalankan komputasi berat di thread terpisah — UI tidak membeku. Hasil dikirim balik via Future.
Uji: jumlahkan 1..100 dengan loop biasa — harus `5050`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
void main() {
// hitung total 1..100 di sini
print(0);
}import "dart:isolate";
Future<void> main() async {
var hasil = await Isolate.run(() {
var total = 0;
for (var i = 1; i <= 1000000; i++) total += i;
return total;
});
print(hasil); // 500000500000
}import, show/hide, as alias, deferred.
import "dart:math"; membawa pustaka core. import "package:nama/lib.dart"; untuk paket eksternal. import "file.dart" show fungsiA; menyaring. as memberi alias: import "dart:math" as m;. deferred as lazy memuat saat dibutuhkan.
as m memberi alias — semua akses lewat m.max, m.min. Tanpa alias, max langsung tersedia.
Uji: max(3,8,1,9,4) — harus `9`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
import "dart:math" as m;
void main() {
print("gunakan m.max di sini");
}import "dart:math" as m;
void main() {
print(m.max(3, 8));
print(m.min(3, 8));
print(m.pow(2, 10));
}jsonEncode/Decode, http package (perlu pub).
dart:convert menyediakan jsonEncode(obj) dan jsonDecode(teks). Untuk HTTP: package:http perlu ditambah di pubspec.yaml lalu pub get. import "package:http/http.dart" as http; http.get(Uri.parse(url)); http.post(url, body: jsonEncode(data)).
jsonEncode mengubah Map ke string JSON; jsonDecode mengubah balik ke Map<String, dynamic>. Akses lewa ["nama"].
Uji: jsonEncode({"umur":17}) → decode → ["umur"], harus `17`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
import "dart:convert";
void main() {
print("encode/decode di sini");
}import "dart:convert";
void main() {
var data = {"nama": "Ari", "umur": 17};
var teks = jsonEncode(data);
print(teks);
var balik = jsonDecode(teks) as Map<String, dynamic>;
print(balik["nama"]);
}group/test/expect — unit test standar Dart.
package:test menyediakan test("deskripsi", () { expect(hasil, harapan); });. group("Kategori", () { ... }); mengelompokkan. expect punya matcher: equals, greaterThan, isTrue, contains, throwsException. Jalankan dengan dart test.
Di proyek lokal: package:test memberi test() dan expect(). Di sini kita simulasi dengan assert() — pola yang sama: kasus → ekspektasi → verifikasi.
Uji: 4 kasus untuk fungsi bagi() — semua lulus, cetak `4/4 lulus`.
Salin kerangka latihan di bawah, lalu tempel ke DartPad dan lengkapi kodenya. Contoh materi tetap tersedia sebagai referensi.
int bagi(int a, int b) {
if (b == 0) throw Exception("nol");
return a ~/ b;
}
void main() {
print("susun kasus di sini");
}// Untuk proyek lokal, jalankan suite test dengan dart test.
// DartPad menjalankan pemeriksaan assert sederhana berikut.
int tambah(int a, int b) => a + b;
void main() {
assert(tambah(2, 3) == 5, "tambah(2,3) harus 5");
assert(tambah(0, 0) == 0, "tambah(0,0) harus 0");
assert(tambah(-1, 1) == 0, "tambah(-1,1) harus 0");
print("3/3 lulus");
}