Pengenalan Dart #
Dart lahir dari frustrasi yang sangat spesifik: JavaScript tidak dirancang untuk aplikasi berskala besar, dan tidak ada jalan mudah untuk memperbaikinya dari dalam. Google, yang menulis puluhan juta baris JavaScript untuk produk-produknya, membutuhkan bahasa yang bisa dikompilasi menjadi JavaScript untuk browser, tapi juga bisa berjalan native di server dan perangkat mobile — dengan sistem tipe yang kuat, tooling yang terintegrasi, dan performa yang bisa diprediksi. Dart adalah jawaban atas masalah itu. Tapi perjalanan Dart tidak linear: bahasa ini hampir tenggelam sebelum akhirnya menemukan tujuan sebenarnya lewat Flutter — dan dengan Flutter, Dart kini menjadi salah satu bahasa dengan pertumbuhan adopsi tercepat di dunia mobile development. Artikel ini membahas dari mana Dart berasal, apa yang membuatnya berbeda secara teknikal, bagaimana ekosistemnya bekerja, dan kapan Dart adalah pilihan yang tepat.
Filosofi Desain Dart #
Dart dirancang dengan satu proposisi utama: bahasa yang bisa menjadi satu sumber kebenaran untuk semua platform — mobile, web, desktop, dan server — tanpa mengorbankan performa atau ergonomi developer.
Tiga prinsip yang membentuk keputusan desain Dart:
Produktivitas developer adalah prioritas pertama. Dart memilih ergonomi yang familiar (sintaks mirip C/Java/JavaScript) agar developer yang datang dari bahasa lain bisa produktif dengan cepat. Hot reload di Flutter — yang memungkinkan melihat perubahan UI dalam milidetik tanpa restart — adalah ekspresi paling nyata dari prinsip ini.
Sound type system, bukan gradual typing. Dart menerapkan sound null safety mulai versi 2.12 — bukan sebagai opsional, melainkan sebagai jaminan. Jika kode kamu dikompilasi tanpa error, compiler menjamin tidak ada null yang bisa masuk ke variabel non-nullable secara diam-diam. Ini berbeda dari TypeScript yang null safety-nya bisa “dibobol” dengan any.
AOT dan JIT dalam satu bahasa. Dart mendukung dua mode kompilasi: JIT (Just-In-Time) untuk development cepat dengan hot reload, dan AOT (Ahead-Of-Time) untuk binary native yang dioptimalkan untuk production. Tidak ada bahasa lain yang memberikan keduanya dengan mulus dalam satu toolchain.
flowchart TD
A[Kode Dart] --> B{Mode Kompilasi}
B -- Development --> C[JIT Compilation]
B -- Production --> D[AOT Compilation]
C --> E[Hot Reload ✓]
C --> F[Debug Info ✓]
D --> G[Native Binary]
D --> H[JavaScript via dart2js]
D --> I[WebAssembly via dart2wasm]
G --> J[Android / iOS / Desktop]
H --> K[Browser]
I --> KSejarah dan Evolusi Dart #
Memahami sejarah Dart penting karena versi-versi awal menciptakan reputasi yang sudah tidak relevan — dan banyak developer masih membawa persepsi lama itu.
Lars Bak dan Kasper Lund, dua engineer yang sebelumnya membangun mesin V8 JavaScript di Google, memperkenalkan Dart pada konferensi GOTO 2011 di Aarhus, Denmark. Dart awalnya dipresentasikan sebagai calon pengganti JavaScript di browser — ambisi yang langsung memicu perlawanan dari komunitas web. Browser vendor selain Google menolak mengimplementasikan Dart VM, dan rencana itu perlahan-lahan ditinggalkan.
| Tahun | Versi | Pencapaian Penting |
|---|---|---|
| 2011 | — | Dart diperkenalkan publik oleh Lars Bak dan Kasper Lund |
| 2013 | 1.0 | Rilis stabil pertama, fokus web dengan compiler dart2js |
| 2015 | — | Flutter mulai dikembangkan menggunakan Dart |
| 2018 | 2.0 | Redesain besar: sound type system, Dart for Flutter |
| 2021 | 2.12 | Sound null safety — perubahan paradigma penanganan null |
| 2021 | 2.13 | Type aliases untuk non-function types |
| 2022 | 2.17 | Enhanced enums, super initializer parameters |
| 2023 | 3.0 | Records, patterns, class modifiers — era Dart modern |
| 2023 | 3.2 | Extension types, interop improvements |
| 2024 | 3.3 | Extension types stabil, Wasm improvements |
Titik balik sesungguhnya adalah Flutter. Ketika Google merilis Flutter 1.0 pada 2018, Dart tiba-tiba memiliki killer use case yang tidak bisa diabaikan: satu codebase untuk Android dan iOS dengan performa mendekati native. Adopsi Dart melonjak, bukan karena developer memilih Dart secara mandiri, tapi karena mereka memilih Flutter dan Dart datang bersamanya.
Dart 2.0 adalah reboot praktis. Hampir semua yang kamu baca tentang Dart sebelum 2018 tidak relevan lagi — sistem tipe berubah fundamental, banyak API didesain ulang, dan bahasa mendapatkan identitas yang jelas sebagai “bahasa Flutter”.
stateDiagram-v2
[*] --> WebEra: 2011-2014
WebEra --> Transisi: Flutter prototype (2015)
Transisi --> FlutterEra: Dart 2.0 + Flutter 1.0 (2018)
FlutterEra --> NullSafety: Dart 2.12 (2021)
NullSafety --> ModernDart: Dart 3.0 (2023)
ModernDart --> [*]
WebEra: Ambisi ganti JavaScript, adopsi rendah
Transisi: Dart jadi bahasa internal Flutter
FlutterEra: Ledakan adopsi via Flutter
NullSafety: Type system menjadi sound sepenuhnya
ModernDart: Records, patterns, extension typesSound Null Safety — Jaminan, Bukan Saran #
Null safety di Dart bukan sekadar fitur tambahan — ini adalah perubahan fundamental cara sistem tipe bekerja. Sejak Dart 2.12, semua variabel non-nullable tidak bisa berisi null kecuali kamu secara eksplisit menyatakan variabel tersebut nullable dengan ?.
// ANTI-PATTERN (era sebelum null safety / bahasa tanpa sound null safety)
String nama; // bisa null kapan saja tanpa peringatan
print(nama.length); // runtime error: Null check operator used on a null value
// BENAR: Dart memaksa deklarasi yang jujur tentang nullability
String namaPasti = "Unis"; // tidak pernah null — dijamin compiler
String? namaMungkinNull; // bisa null — kamu harus handle ini secara eksplisit
// Compiler menolak ini:
// print(namaMungkinNull.length); // ERROR: The property 'length' can't be unconditionally accessed
// Cara yang benar menangani nullable
print(namaMungkinNull?.length); // null-aware access: return null jika null
print(namaMungkinNull?.length ?? 0); // null-coalescing: fallback ke 0
print(namaMungkinNull!.length); // null assertion: kamu yakin tidak null (throw jika null)
if (namaMungkinNull != null) {
print(namaMungkinNull.length); // promotion: di dalam blok ini, Dart tahu tidak null
}
Operator!(null assertion) adalah escape hatch yang harus dipakai dengan sangat hati-hati. Jika variabel ternyatanullsaat runtime, program akan throwNull check operator used on a null value. Ini adalah satu-satunya cara “menipu” null safety Dart — dan itu memang disengaja supaya mudah di-audit. Preferensikan??,?., atau conditional check daripada!.
Efek null safety melampaui sekadar mencegah crash. Compiler bisa mengoptimalkan kode lebih agresif karena dia tahu nilai mana yang tidak akan pernah null — menghasilkan binary yang lebih cepat dan lebih kecil.
Sistem Tipe Dart #
Dart adalah bahasa dengan tipe statis (statically typed) tapi dengan type inference yang kuat — kamu tidak selalu perlu menulis tipe secara eksplisit.
Type Inference dengan var dan final
#
// var — tipe disimpulkan dari nilai awal, tidak bisa diubah tipenya
var nama = "Unis"; // disimpulkan sebagai String
var umur = 25; // disimpulkan sebagai int
var tinggi = 175.5; // disimpulkan sebagai double
// ANTI-PATTERN: mendeklarasikan tipe secara eksplisit saat sudah jelas
String nama2 = "Unis"; // redundan — var sudah cukup
int umur2 = 25; // redundan — var sudah cukup
// BENAR: pakai tipe eksplisit saat memperjelas intent
String? emailNullable; // perlu eksplisit karena nullable
List<String> daftarNama = []; // perlu eksplisit untuk generics yang kosong
// final — assigned sekali, tidak bisa di-reassign
final namaPengguna = "Unis";
// namaPengguna = "Lain"; // ERROR: The final variable can't be assigned
// const — compile-time constant
const pi = 3.14159;
const maxRetry = 3;
Generics #
Generics di Dart adalah reified — informasi tipe tersedia saat runtime, bukan hanya saat kompilasi seperti di Java (type erasure). Ini berarti kamu bisa melakukan introspeksi tipe secara nyata.
// List dengan tipe spesifik
List<int> angka = [1, 2, 3, 4, 5];
List<String> nama = ["Alice", "Bob", "Charlie"];
// Map dengan generics
Map<String, int> skor = {
"Alice": 95,
"Bob": 87,
};
// Function generic
T pertama<T>(List<T> list) {
if (list.isEmpty) throw ArgumentError("List kosong");
return list[0];
}
int angkaPertama = pertama([10, 20, 30]); // => 10
String namaPertama = pertama(["Alice", "Bob"]); // => "Alice"
// Reified generics — tipe tersedia di runtime
print(angka is List<int>); // true
print(angka is List<String>); // false — bukan List<String>!
// Di Java: keduanya true karena type erasure
Model Concurrency: Isolate #
Dart tidak menggunakan thread model seperti Java atau Go. Dart menggunakan Isolate — unit eksekusi yang benar-benar terisolasi, tidak berbagi memori satu sama lain. Komunikasi antar Isolate dilakukan lewat message passing, bukan shared state.
flowchart LR
A[Main Isolate\nHeap A] -- send message --> B[Worker Isolate\nHeap B]
B -- send result --> A
C[Isolate C\nHeap C] -- send message --> A
style A fill:#4f86c6,color:#fff
style B fill:#5aaf6a,color:#fff
style C fill:#e0884f,color:#fffModel ini menghilangkan data race secara arsitektural — bukan dengan lock atau mutex, tapi karena tidak ada memori yang bisa di-share. Ini berbeda dari Go (goroutine berbagi heap) atau Java (thread berbagi heap dengan sinkronisasi manual).
Untuk sebagian besar kebutuhan concurrency di Dart, async/await berbasis Future dan Stream sudah cukup — Isolate untuk task CPU-intensive yang bisa memblokir UI thread.
import 'dart:async';
import 'dart:isolate';
// async/await — untuk I/O non-blocking
Future<String> ambilData(String url) async {
// simulasi network request
await Future.delayed(Duration(seconds: 2));
return "Data dari $url";
}
Future<void> main() async {
print("Mulai fetch...");
// Sequential — tunggu satu per satu
final data1 = await ambilData("https://api.example.com/users");
final data2 = await ambilData("https://api.example.com/posts");
// LEBIH BAIK: Parallel — jalankan keduanya bersamaan
final hasil = await Future.wait([
ambilData("https://api.example.com/users"),
ambilData("https://api.example.com/posts"),
]);
print("Selesai: ${hasil[0]}, ${hasil[1]}");
}
// Isolate — untuk komputasi berat yang tidak boleh blokir UI
Future<List<int>> hitungPrimaBerat(int batas) async {
// Jalankan di Isolate terpisah agar UI tetap responsif
return await Isolate.run(() {
final prima = <int>[];
for (int i = 2; i <= batas; i++) {
bool isPrima = true;
for (int j = 2; j * j <= i; j++) {
if (i % j == 0) { isPrima = false; break; }
}
if (isPrima) prima.add(i);
}
return prima;
});
}
Stream — Data Asinkron yang Berkelanjutan #
Future untuk nilai tunggal di masa depan, Stream untuk sequence nilai asinkron yang berkelanjutan — seperti event user, data sensor, atau WebSocket messages.
// Stream sederhana
Stream<int> hitunganMundur(int dari) async* {
for (int i = dari; i >= 0; i--) {
await Future.delayed(Duration(seconds: 1));
yield i; // emit nilai satu per satu
}
}
Future<void> main() async {
// Dengarkan stream
await for (final angka in hitunganMundur(5)) {
print("Hitung mundur: $angka");
}
// Atau dengan listen
hitunganMundur(3).listen(
(angka) => print("Nilai: $angka"),
onError: (e) => print("Error: $e"),
onDone: () => print("Stream selesai"),
);
}
Fitur Modern: Dart 3.x #
Dart 3.0 yang dirilis 2023 membawa tiga fitur besar yang mengubah cara kode Dart ditulis secara signifikan.
Records #
Record adalah tipe data anonymous yang immutable untuk mengelompokkan beberapa nilai tanpa harus mendefinisikan class baru.
// ANTI-PATTERN: menggunakan List atau Map yang tidak type-safe
List hasilDivide(int a, int b) {
return [a ~/ b, a % b]; // caller tidak tahu posisi mana hasil dan sisa
}
// BENAR: Record — type-safe, named fields opsional
(int hasil, int sisa) bagi(int a, int b) {
return (a ~/ b, a % b);
}
void main() {
final (hasil, sisa) = bagi(17, 5);
print("17 / 5 = $hasil sisa $sisa"); // => 17 / 5 = 3 sisa 2
// Named fields
({String nama, int umur}) profil = (nama: "Unis", umur: 28);
print(profil.nama); // => Unis
}
Pattern Matching #
Pattern matching memungkinkan destructuring dan conditional logic yang jauh lebih ekspresif.
sealed class Bentuk {}
class Lingkaran extends Bentuk { final double radius; Lingkaran(this.radius); }
class Persegi extends Bentuk { final double sisi; Persegi(this.sisi); }
class Segitiga extends Bentuk { final double alas, tinggi; Segitiga(this.alas, this.tinggi); }
double hitungLuas(Bentuk bentuk) => switch (bentuk) {
Lingkaran(:final radius) => 3.14159 * radius * radius,
Persegi(:final sisi) => sisi * sisi,
Segitiga(:final alas, :final tinggi) => 0.5 * alas * tinggi,
};
void main() {
final bentuk = Lingkaran(5);
print("Luas: ${hitungLuas(bentuk)}"); // => Luas: 78.53975
// Pattern di if statement
final nilai = (nama: "Unis", skor: 95);
if (nilai case (nama: final n, skor: >= 90)) {
print("$n lulus dengan nilai A");
}
}
Sealed Classes #
sealed memaksa exhaustiveness checking — compiler memastikan kamu menangani semua subtype yang mungkin, seperti enum tapi dengan data.
sealed class HasilOperasi<T> {}
class Sukses<T> extends HasilOperasi<T> {
final T data;
Sukses(this.data);
}
class Gagal<T> extends HasilOperasi<T> {
final String pesan;
final Exception? exception;
Gagal(this.pesan, [this.exception]);
}
class Loading<T> extends HasilOperasi<T> {}
// Compiler MEMAKSA kamu handle semua case — tidak ada yang terlewat
String deskripsiHasil(HasilOperasi<String> hasil) => switch (hasil) {
Sukses(:final data) => "Berhasil: $data",
Gagal(:final pesan) => "Gagal: $pesan",
Loading() => "Sedang memuat...",
// Tidak perlu default — compiler tahu ini exhaustive
};
Ekosistem: pub.dev dan Dart Tooling #
pub.dev adalah registry package resmi Dart dan Flutter. Tooling Dart terintegrasi dalam satu perintah dart:
# Manajemen project
dart create nama_project # buat project baru (console app)
dart create -t web nama_project # web project
dart create -t package nama_lib # library/package
# Package management
dart pub get # install dependensi dari pubspec.yaml
dart pub add http # tambah dependensi
dart pub add --dev mockito # tambah dev dependency
dart pub upgrade # update semua package
# Build, run, compile
dart run # jalankan project
dart run bin/main.dart # jalankan file spesifik
dart compile exe bin/main.dart # compile ke native executable
dart compile js bin/main.dart # compile ke JavaScript
dart compile wasm bin/main.dart # compile ke WebAssembly
# Quality
dart analyze # static analysis
dart format . # format semua file
dart test # jalankan semua test
dart doc # generate dokumentasi
Contoh pubspec.yaml untuk project Dart murni (non-Flutter):
name: aplikasi_saya
description: Contoh aplikasi Dart dengan dependensi umum
version: 1.0.0
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
http: ^1.1.0 # HTTP client
json_annotation: ^4.8.0 # JSON serialization
args: ^2.4.2 # CLI argument parsing
logging: ^1.2.0 # Logging
dev_dependencies:
test: ^1.24.0
mockito: ^5.4.0
build_runner: ^2.4.0
json_serializable: ^6.7.0
lints: ^3.0.0 # official lint rules
Package Populer per Kategori #
| Kategori | Package | Kegunaan |
|---|---|---|
| HTTP Client | http, dio | Request ke API eksternal |
| JSON | json_serializable, freezed | Code generation untuk serialization |
| State Management | riverpod, bloc, provider | State management di Flutter |
| Database | drift, isar, hive | Local database |
| DI / Service Locator | get_it, injectable | Dependency injection |
| Testing | test, mockito, mocktail | Unit dan integration test |
| CLI | args, dcli | Command-line applications |
| Code Generation | build_runner, source_gen | Kode yang di-generate otomatis |
Dart di Luar Flutter #
Salah satu miskonsepsi terbesar tentang Dart: banyak developer mengira Dart hanya bisa dipakai untuk Flutter. Padahal Dart adalah bahasa general-purpose yang bisa dipakai mandiri.
CLI Applications — Dart mengkompilasi ke native binary yang bisa didistribusikan sebagai file tunggal tanpa runtime dependency. Ini membuatnya sangat cocok untuk tooling dan CLI apps.
// bin/main.dart — CLI app sederhana
import 'dart:io';
import 'package:args/args.dart';
void main(List<String> arguments) {
final parser = ArgParser()
..addOption('nama', abbr: 'n', defaultsTo: 'Dunia')
..addFlag('sapa', abbr: 's', defaultsTo: false);
final results = parser.parse(arguments);
if (results['sapa'] as bool) {
print("Halo, ${results['nama']}! Selamat datang di Dart CLI.");
} else {
print("${results['nama']}");
}
}
Server-side — Framework seperti shelf dan dart_frog memungkinkan kamu menulis REST API atau backend service dalam Dart. Google sendiri menggunakan Dart untuk beberapa backend service internalnya.
import 'dart:io';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:shelf_router/shelf_router.dart';
void main() async {
final router = Router()
..get('/health', (Request req) => Response.ok('OK'))
..get('/api/users/<id>', (Request req, String id) {
return Response.ok('{"id": "$id", "nama": "Pengguna $id"}',
headers: {'content-type': 'application/json'});
});
final server = await shelf_io.serve(router, 'localhost', 8080);
print('Server berjalan di http://${server.address.host}:${server.port}');
}
Kapan Memilih Dart #
Dart bukan pilihan terbaik untuk semua situasi. Keputusan yang tepat bergantung pada konteks proyekmu.
Pilih Dart jika:
✓ Kamu membangun aplikasi Flutter (mobile/desktop/web)
✓ Kamu butuh satu codebase untuk Android, iOS, dan web
✓ Tim sudah familiar dengan Dart dari proyek Flutter sebelumnya
✓ Kamu membangun CLI tool yang perlu dikompilasi ke native binary
✓ Sound null safety dan type system yang kuat adalah kebutuhan
✓ Hot reload / fast development cycle adalah prioritas
Pertimbangkan alternatif jika:
✗ Kamu membangun backend API standalone → Go, Kotlin, Python lebih mature ekosistemnya
✗ Proyek ML/AI → Python tidak tergantikan
✗ Tim hanya familiar web dan tidak butuh mobile → TypeScript + React lebih relevan
✗ Kamu butuh ekosistem server-side yang luas dan proven → Node.js, Java, Go
✗ Native iOS development tanpa Flutter → Swift adalah pilihan wajar
✗ Native Android tanpa Flutter → Kotlin lebih natural
| Kriteria | Dart (Flutter) | React Native | Swift/Kotlin | Kotlin Multiplatform |
|---|---|---|---|---|
| Satu codebase | ✓ Penuh | ✓ Penuh | ✗ Terpisah | ✓ Sebagian |
| Performa UI | ★★★★★ | ★★★☆☆ | ★★★★★ | ★★★★★ |
| Ekosistem | ★★★★☆ | ★★★★☆ | ★★★★★ | ★★★☆☆ |
| Kemudahan belajar | ★★★★☆ | ★★★★☆ | ★★★☆☆ | ★★★☆☆ |
| Hot reload | ★★★★★ | ★★★★☆ | ★★☆☆☆ | ★★★☆☆ |
| Web support | ★★★★☆ | ★★★☆☆ | ✗ | ✗ |
FAQ #
Apakah Dart bisa dipakai tanpa Flutter?
Ya, sepenuhnya. Dart adalah bahasa general-purpose yang bisa dikompilasi ke native binary (untuk CLI/server), JavaScript (untuk web), atau WebAssembly. Flutter adalah framework yang dibangun di atas Dart — bukan sebaliknya. Kamu bisa belajar dan memakai Dart tanpa pernah menyentuh Flutter.
Apa perbedaan var, final, dan const di Dart?
var adalah variabel yang bisa di-reassign (tipe disimpulkan dari nilai awal). final adalah variabel yang hanya bisa di-assign sekali saat runtime. const adalah compile-time constant — nilainya harus bisa dievaluasi saat kompilasi. Gunakan const sebanyak mungkin untuk performa, final untuk nilai yang hanya di-set sekali, dan var untuk yang lainnya.
Apakah Dart mendukung functional programming?
Dart mendukung gaya functional programming dengan baik: higher-order functions, closures, lambda, immutable collections, dan — sejak Dart 3 — pattern matching dan records. Dart bukan bahasa purely functional, tapi sangat mendukung gaya hybrid.
Bagaimana Dart menangani dependency injection?
Dart tidak punya built-in DI, tapi ekosistem package-nya sudah mature. get_it adalah service locator yang paling populer. Untuk proyek Flutter yang lebih besar, injectable bersama get_it menyediakan DI berbasis annotation yang mirip Spring di Java. riverpod juga sering dipakai sebagai alternatif DI sekaligus state management.
Apakah Dart stabil untuk production?
Sangat stabil. Dart 2.x sudah dipakai di production sejak 2018 dan Dart 3.x (2023) adalah rilis major dengan perubahan semua breaking change yang sudah di-manage dengan baik. Google menggunakannya secara internal, dan Shopify, BMW, eBay, dan ribuan startup menggunakan Flutter (dan Dart) untuk aplikasi production mereka.
Ringkasan #
- Dart lahir dari kebutuhan nyata Google — bukan proyek riset akademis, melainkan solusi untuk skala pengembangan JavaScript yang tidak bisa dipecahkan dari dalam. Tujuan awalnya bergeser, tapi hasilnya adalah bahasa yang jauh lebih solid.
- Sound null safety adalah jaminan compiler — bukan sekadar lint warning. Jika kode Dart-mu dikompilasi bersih, tidak ada
nullyang bisa masuk ke variabel non-nullable secara diam-diam. Ini menghilangkan seluruh kelas bug yang umum.- JIT untuk development, AOT untuk production — dua mode kompilasi ini bekerja dalam satu toolchain. Hot reload yang milidetik saat development, binary native yang dioptimalkan saat production.
- Isolate bukan thread — Dart menghilangkan data race secara arsitektural dengan Isolate yang tidak berbagi memori. Komunikasi lewat message passing, bukan shared state.
async/awaitberbasis Future dan Stream — untuk sebagian besar kebutuhan async, gunakanFutureuntuk nilai tunggal danStreamuntuk data berkelanjutan. Isolate hanya untuk komputasi CPU-intensive.- Dart 3.x membawa records, patterns, dan sealed classes — fitur-fitur ini mengubah cara kamu menulis kode yang benar dan exhaustive. Pattern matching khususnya menghilangkan banyak kebutuhan akan type casting manual.
- Dart bukan hanya Flutter — Dart bisa dikompilasi ke native CLI binary, JavaScript, dan WebAssembly. Backend dengan
shelf/dart_frog, CLI tool, dan web app semuanya bisa ditulis dalam Dart murni.- pub.dev adalah ekosistem yang mature — terutama di kategori Flutter, tapi package untuk server-side dan CLI juga terus berkembang. Tooling
dartCLI terintegrasi untuk semua kebutuhan — package, format, analyze, test, dan compile.
Berikutnya: Instalasi →