Dart Language #

Bahasa pemrograman adalah fondasi utama dari setiap framework aplikasi. Mengembangkan aplikasi Flutter tanpa memahami bahasa penggeraknya, Dart, seperti membangun rumah di atas pasir. Dart bukan sekadar bahasa perantara; ia adalah bahasa yang dirancang khusus oleh Google untuk mengoptimalkan pengembangan antarmuka pengguna (client-side development) dengan siklus iterasi yang cepat dan performa eksekusi murni. Kita akan membedah bahasa Dart secara menyeluruh: mulai dari sistem tipe data statis, mekanisme pertahanan Null Safety, kekuatan pemrograman berorientasi objek (OOP), fungsionalitas koleksi, model konkurensi asinkron, hingga fitur-fitur modern Dart 3.x seperti Records, Pattern Matching, dan Extension Methods.


Deklarasi Variabel & Kata Kunci Kontrol #

Dart adalah bahasa pemrograman yang strongly typed (bertipe kuat). Ini berarti setiap variabel wajib memiliki tipe data yang pasti sebelum dieksekusi. Namun, Dart juga dilengkapi dengan fitur type inference (penyimpulan tipe otomatis) melalui kata kunci var. Compiler Dart cukup cerdas untuk menganalisis nilai awal variabel dan menyimpulkan tipenya secara otomatis saat waktu kompilasi.

Pembeda Kata Kunci Variabel #

Dalam menulis kode Dart, kita memiliki tiga kata kunci utama untuk mendeklarasikan variabel berdasarkan tingkat mutabilitasnya:

Kata KunciMutabilitas (Bisa Diubah?)Waktu Penetapan Nilai (Binding Time)Contoh Penggunaan
var✅ Ya (Nilai di dalam variabel dapat ditimpa dengan tipe yang sama)Dinamis saat runtimeMenyimpan data input form sementara.
final❌ Tidak (Hanya bisa di-set satu kali saja)Evaluasi saat runtime (ketika baris kode dijalankan)Menyimpan waktu saat ini (DateTime.now()).
const❌ Tidak (Mutlak tidak dapat diubah)Evaluasi saat compile-time (harus sudah diketahui saat build)Menyimpan nilai matematika konstan ($\pi = 3.14$).
// Contoh penggunaan final vs const
final DateTime waktuSekarang = DateTime.now(); // ✓ BENAR (Nilai baru diketahui saat runtime)
// const DateTime waktuGagal = DateTime.now(); // ✗ ERROR (const menuntut nilai konstan sejak build-time)

const double pi = 3.14159; // ✓ BENAR (Nilai sudah pasti dan konstan)

Tipe Data Bawaan (Built-in Types) #

Dart menyediakan kelas fundamental untuk menangani data dasar:

  • int dan double: Keduanya mewakili tipe angka. int untuk bilangan bulat, double untuk bilangan desimal 64-bit. Keduanya merupakan turunan dari kelas num.
  • String: Digunakan untuk representasi teks UTF-16. Dart mendukung interpolasi string menggunakan simbol $ secara langsung.
  • bool: Representasi nilai kebenaran Boolean, hanya memuat nilai true or false.

Sound Null Safety: Menghilangkan Kesalahan Null Pointer #

Tantangan terbesar dalam pengembangan software modern adalah menghindari Null Pointer Exceptions (kesalahan referensi variabel kosong). Sejak versi 2.12, Dart memperkenalkan sistem Sound Null Safety.

  • Sound (Mutlak): Berarti compiler Dart memberikan jaminan mutlak di tingkat runtime bahwa variabel yang dideklarasikan sebagai non-nullable tidak akan pernah bernilai null. Hal ini mengizinkan compiler menghasilkan biner mesin yang lebih kecil dan mengeksekusi kode dengan lebih cepat karena tidak perlu melakukan pengecekan null berulang kali di runtime.

Secara default, seluruh deklarasi variabel di Dart adalah non-nullable:

String nama = 'Budi'; // Tipe non-nullable
// nama = null; // ✗ ERROR (Ditangkap langsung oleh compiler sebelum aplikasi jalan)

String? namaNullable = 'Budi'; // Tipe nullable (dengan tanda tanya ?)
namaNullable = null; // ✓ BENAR (Null diizinkan secara eksplisit)

Operator Pengaman Null (Null-aware Operators) #

Untuk mempermudah penanganan variabel yang boleh bernilai null, Dart menyediakan rangkaian operator khusus:

String? inputPengguna;

// 1. Operator ?? (Null Coalescing)
// Mengembalikan nilai default jika variabel di sebelah kiri bernilai null
String dataTampil = inputPengguna ?? 'Tamu'; 

// 2. Operator ?. (Conditional Member Access)
// Mengakses properti hanya jika objek tidak null, mencegah crash aplikasi
int? panjangTeks = inputPengguna?.length;

// 3. Operator ??= (Assignment if Null)
// Mengisi nilai ke variabel hanya jika variabel tersebut saat ini bernilai null
inputPengguna ??= 'Nilai Baru';
Hindari penggunaan operator seru (!) secara agresif. Operator ! (null assertion operator) memaksa compiler menganggap variabel nullable pasti tidak null saat itu. Jika ternyata variabel tersebut bernilai null saat runtime, aplikasi akan langsung mengalami crash (uncaught exception). Lebih aman menggunakan operator ?? atau conditional check if (x != null).

Struktur & Fleksibilitas Fungsi #

Fungsi di Dart adalah first-class citizens. Ini berarti fungsi dianggap sebagai objek biasa. Fungsi dapat disimpan ke dalam variabel, diteruskan sebagai parameter ke fungsi lain (callbacks), atau dikembalikan sebagai nilai dari suatu fungsi.

Parameter Bernama (Named Parameters) #

Flutter sangat bergantung pada konsep Named Parameters untuk menyusun widget trees. Pilihan parameter ini dibungkus menggunakan tanda kurung kurawal {}:

// Mendefinisikan fungsi dengan parameter bernama
void buatContainer({
  required String judul,      // Wajib diisi saat pemanggilan
  double? lebar,              // Opsional dan boleh null
  double tinggi = 100.0,      // Opsional dengan nilai default
}) {
  // Logika rendering
}

// Pemanggilan fungsi (parameter diidentifikasi lewat namanya, urutan tidak masalah)
buatContainer(
  tinggi: 250.0,
  judul: 'Tombol Utama',
);

Named parameters meningkatkan keterbacaan kode secara signifikan saat kita harus menyusun puluhan parameter bertingkat di dalam widget Flutter.

Named Parameters dalam Widget Flutter Ketika kita menulis Container(padding: EdgeInsets.all(8), child: Text('Halo')), kita sedang memanggil konstruktor kelas Container yang menerapkan named parameters. Hal ini membuat kode deklaratif UI sangat mudah dibaca.

Pemrograman Berorientasi Objek (OOP) Terstruktur #

Dart mengadopsi paradigma pemrograman berorientasi objek penuh berbasis kelas (class-based OOP). Semua nilai yang kita manipulasi di Dart adalah instansi dari suatu kelas — bahkan angka dan fungsi.

Constructor & Initializer List #

Dart menyederhanakan deklarasi properti di konstruktor melalui fitur sugar syntax this.propertyName:

class Mobil {
  final String merek;
  final double kecepatanMaksimal;
  
  // Konstruktor ringkas Dart
  Mobil({
    required this.merek,
    required this.kecepatanMaksimal,
  });

  // Named Constructor (Alternatif pembuat objek dengan nama khusus)
  Mobil.listrik({required String merek})
      : merek = merek,
        kecepatanMaksimal = 180.0; // Initializer list
}

Mixin: Komposisi Fungsional Tanpa Pewarisan Ganda #

Banyak bahasa pemrograman melarang pewarisan ganda (multiple inheritance) karena masalah ambiguitas (diamond problem). Dart mengatasi hal ini dengan memperkenalkan Mixins — cara menggunakan kembali kode kelas di beberapa hierarki kelas tanpa harus melakukan inheritance berantai.

Relasi perakitan mixin ini diilustrasikan di bawah ini:

flowchart TD
    Base["Abstract Class: Hewan"] --> Sub["Class: Bebek"]
    
    subgraph MixinContainer["Mixins (Kemampuan Tambahan)"]
        M1["Mixin: BisaTerbang"]
        M2["Mixin: BisaBerenang"]
    end
    
    MixinContainer -. "with" .-> Sub
    
    style Base stroke:#0288d1,stroke-width:2px
    style Sub stroke:#388e3c,stroke-width:2px
    style MixinContainer stroke:#f57c00,stroke-width:2px

Implementasi mixin dalam kode Dart ditulis menggunakan kata kunci with:

abstract class Hewan {
  final String nama;
  Hewan(this.nama);
}

mixin BisaTerbang {
  void terbang() => print('Sedang terbang tinggi!');
}

mixin BisaBerenang {
  void berenang() => print('Sedang berenang di air!');
}

// Bebek mewarisi Hewan dan merakit kemampuan dari BisaTerbang & BisaBerenang
class Bebek extends Hewan with BisaTerbang, BisaBerenang {
  Bebek(super.nama);
}

void main() {
  final donald = Bebek('Donald');
  donald.terbang();   // Output: Sedang terbang tinggi!
  donald.berenang();  // Output: Sedang berenang di air!
}

Operasi Koleksi & Paradigma Fungsional #

Dart menyediakan kelas koleksi bawaan yang sangat fleksibel:

  • List: Kumpulan data berurutan (sering disebut array di bahasa lain).
  • Set: Kumpulan data unik tidak berurutan (duplikat otomatis dihapus).
  • Map: Kumpulan pasangan kunci-nilai (key-value pairs).

Operasi Fungsional pada List #

Kita dapat memanipulasi koleksi menggunakan ekspresi fungsional (tanpa mengubah list asli/immutability):

final List<int> angka = [1, 2, 3, 4, 5];

// 1. map() - mentransformasikan setiap elemen
final List<int> kuadrat = angka.map((n) => n * n).toList(); // [1, 4, 9, 16, 25]

// 2. where() - memfilter elemen berdasarkan kondisi (filter)
final List<int> ganjil = angka.where((n) => n % 2 != 0).toList(); // [1, 3, 5]

// 3. reduce() - menggabungkan elemen menjadi satu nilai akhir
final int total = angka.reduce((value, element) => value + element); // 15

Spread Operator, Collection If, & Collection For #

Dart memiliki fitur unik yang sangat berguna saat menyusun struktur anak-anak widget secara dinamis di Flutter:

bool tunjukkanMenuAdmin = true;
final List<String> menuDasar = ['Beranda', 'Profil'];

final List<String> menuLengkap = [
  ...menuDasar,                          // Spread operator (memecah list)
  if (tunjukkanMenuAdmin) 'Admin Panel', // Collection If (kondisional)
  for (var i = 1; i <= 3; i++) 'Item $i'  // Collection For (perulangan)
];
// Hasil: ['Beranda', 'Profil', 'Admin Panel', 'Item 1', 'Item 2', 'Item 3']

Model Konkurensi: Asynchronous & Isolate #

Untuk menjamin kelancaran antarmuka pengguna pada 60-120 FPS, Dart menerapkan model konkurensi yang sangat efisien untuk menangani proses I/O dan komputasi berat.

1. Pemrograman Asinkron (Event Loop, Future, & Stream) #

Secara default, Dart berjalan pada alur eksekusi tunggal (single-threaded execution). Proses penantian data jaringan (network call) atau pembacaan database tidak boleh memblokir thread utama UI. Dart menggunakan Event Loop untuk mengatur antrean tugas asinkron.

  • Future: Merepresentasikan nilai yang akan tersedia di masa depan (misalnya hasil request HTTP). Kita mengelolanya secara deklaratif menggunakan kata kunci async dan await.
  • Stream: Merepresentasikan aliran data asinkron berkelanjutan (seperti listening perubahan lokasi GPS atau koneksi WebSocket).
// Mengambil data dari server secara asinkron (non-blocking)
Future<String> ambilDataAPI() async {
  // Menunggu simulasi delay jaringan selama 2 detik
  await Future.delayed(const Duration(seconds: 2));
  return 'Data Berhasil Dimuat';
}

void main() async {
  print('Memulai Request...');
  final hasil = await ambilDataAPI(); // Thread utama tidak diblokir
  print(hasil);
}

2. Isolate: Konkurensi Multithreading Tanpa Shared Memory #

Ketika kita harus memproses komputasi matematika yang sangat berat (seperti memproses file JSON berukuran 50MB atau manipulasi piksel gambar), menulis kode async/await biasa tidak cukup. Mengapa? Karena kalkulasi berat tersebut akan tetap dieksekusi di UI Thread (Root Isolate) dan membekukan render layar (jank).

Untuk mengatasi hal ini, Dart menyediakan Isolate. Isolate adalah thread khusus yang memiliki memori dan event loop sendiri. Isolate tidak berbagi memori secara langsung dengan isolate lainnya, sehingga tidak memerlukan mekanisme penguncian memori (lock) yang rumit.

import 'dart:isolate';

// Fungsi komputasi berat yang akan dijalankan di Isolate terpisah
void hitungBerat(SendPort portKirim) {
  int total = 0;
  for (int i = 0; i < 1000000000; i++) {
    total += i;
  }
  // Mengirim hasil kembali ke Isolate utama
  portKirim.send(total);
}

void main() async {
  // Membuat port penerima untuk menangkap hasil dari Isolate baru
  final portTerima = ReceivePort();
  
  // Memulai Isolate baru di latar belakang
  await Isolate.spawn(hitungBerat, portTerima.sendPort);
  
  // Menunggu pesan masuk dari Isolate
  portTerima.listen((pesan) {
    print('Hasil kalkulasi berat: $pesan');
    portTerima.close(); // Menutup port setelah selesai
  });
  
  print('Isolate utama tetap bebas merender UI...');
}

Fitur Modern Dart 3.x #

Pembaruan mayor Dart 3.x berfokus pada modernisasi bahasa agar setara dengan bahasa sistem modern seperti Rust atau Swift.

1. Records (Tuple) #

Records memungkinkan kita mengelompokkan beberapa nilai berbeda ke dalam satu objek tunggal yang kompak, tanpa perlu membuat kelas penampung khusus. Records mendukung nama parameter maupun urutan:

// Fungsi mengembalikan dua tipe data berbeda secara bersamaan
(double lat, double lng) dapatkanKoordinat() {
  return (-6.2000, 106.8166);
}

void main() {
  // Destructuring (memecah nilai record secara langsung)
  final (latitude, longitude) = dapatkanKoordinat();
  print('Lat: $latitude, Lng: $longitude');
}

2. Pattern Matching #

Sistem pencocokan pola (Pattern Matching) di Dart 3 mempermudah validasi struktur data yang kompleks secara deklaratif, terutama ketika dikombinasikan dengan pernyataan switch:

void prosesResponse(Object response) {
  switch (response) {
    // Mencocokkan jika response adalah List berisi dua string
    case [String status, String pesan]:
      print('Status: $status, Pesan: $pesan');
    // Mencocokkan jika response adalah Map dengan key spesifik
    case {'error': int code}:
      print('Terjadi error berkode: $code');
    case _:
      print('Format tidak dikenal');
  }
}

3. Extension Methods #

Extension Methods memungkinkan kita menyuntikkan metode baru ke dalam kelas-kelas yang sudah ada (termasuk kelas bawaan SDK seperti String atau int) tanpa perlu melakukan pewarisan kelas atau memodifikasi source code asli.

// Menambahkan method baru pada kelas bawaan String
extension ValidasiEmail on String {
  bool get isValidEmail {
    return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(this);
  }
}

void main() {
  String email = '[email protected]';
  print(email.isValidEmail); // Output: true (Metode dipanggil seperti fungsi bawaan)
}

Matriks Perbandingan Bahasa Modern #

Berikut perbandingan posisi strategis Dart dibandingkan bahasa pemrograman modern industri lainnya:

Fitur BahasaDartKotlinSwiftJavaScript / TS
Null Safety✅ Sound (Mutlak)✅ Sound✅ Sound⚠️ Terbatas (TS)
Kompilasi AOT & JIT✅ Keduanya (Dual)⚠️ JVM (JIT murni)✅ AOT murni❌ JIT (Browser)
Sistem Komposisi✅ Mixins⚠️ Interfaces✅ Protocols❌ Tidak ada
Records / Tuples✅ Ya (Dart 3+)✅ Ya✅ Ya⚠️ Terbatas (Array)
Extension Methods✅ Ya✅ Ya✅ Ya❌ Tidak ada

Ringkasan #

  • Variabel Statis Terarah — Menggunakan sistem bertipe kuat (strongly typed) dengan penyimpulan otomatis (type inference) melalui var, serta pemisah final/const yang jelas.
  • Sound Null Safety — Pertahanan mutlak di tingkat compiler terhadap kesalahan runtime akibat variabel kosong (null reference error).
  • Named Parameters — Fleksibilitas pemanggilan parameter fungsi menggunakan nama untuk keterbacaan kode yang maksimal dalam widget tree.
  • Mixin Komposisi — Mengizinkan penambahan kemampuan fungsionalitas lintas kelas menggunakan kata kunci with tanpa kendala pewarisan ganda.
  • Collection Dinamis — Dukungan mutakhir terhadap spread operator, collection if, dan collection for untuk manipulasi daftar widget dinamis.
  • Model Konkurensi Handal — Penanganan asinkron murni menggunakan Future / Stream serta pemrosesan paralel terisolasi tanpa hambatan memori via Isolate.
  • Fitur Modern Dart 3 — Menyertakan Records untuk pengembalian nilai berganda, Pattern Matching untuk ekstraksi terstruktur, dan Extension Methods untuk menyuntikkan fungsi kustom ke kelas bawaan.

← Sebelumnya: UI Framework   Berikutnya: Engine, Framework & Embedder →

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