Back to IF3210 Pengembangan Aplikasi Piranti Bergerak

Dart Null Safety and Type System

Questions/Cues

  • Apa perbedaan fundamental antara final dan const yang sering membingungkan?
  • Kapan menggunakan tipe Object vs dynamic dan apa risikonya?
  • Bagaimana operator ?., ??, ??=, dan ! bekerja secara konkret?
  • Apa itu kata kunci late dan masalah apa yang dipecahkannya?
  • Mengapa sound null safety dianggap memberi keuntungan performa di production?

Reference Points

  • IF3210 Pengembangan Aplikasi Piranti Bergerak (Slides 13b: 3-14)

final vs const: Perbedaan yang Kritis

final dan const sama-sama mencegah reassignment, tetapi memiliki semantik yang sangat berbeda:

Aspekfinalconst
Waktu evaluasiRuntimeCompile-time
Kedalaman immutabilityReferensi saja (shallow)Seluruh objek (deep)
Isi objekBisa berubahTidak bisa berubah
CanonicalizationTidakYa (satu salinan di memori)
Contoh validfinal list = [1,2,3]; list.add(4);const list = [1,2,3]; // list.add(4) ERROR

final berarti variabel hanya memiliki satu referensi — tidak bisa diarahkan ke objek lain — tetapi objek yang dirujuk masih dapat dimutasi. const berarti seluruh objek bersifat deeply immutable dan dikompilasi sebagai nilai tetap.

Canonicalization: dua instans const dengan nilai yang sama adalah objek yang identik di memori (dapat diverifikasi dengan identical()). Ini berarti Dart hanya membuat satu salinan untuk semua widget const yang identik, yang menjadi alasan utama mengapa widget const direkonstruksi lebih cepat dalam Flutter.

Immutable Classes dengan const Constructor

Untuk membuat class yang seluruh instansnya bersifat deeply immutable, definisikan const constructor dengan semua field bertipe final:

  • Semua instance fields wajib final
  • Konstruktor dideklarasikan dengan kata kunci const
  • Instans yang dibuat dengan const MyClass(...) dijamin identik jika parameternya sama (identical() mengembalikan true)

Pola ini umum digunakan di Flutter untuk widget dan data classes yang tidak perlu berubah, mengoptimalkan proses rebuild tree widget.

Object vs dynamic: Keamanan Tipe

Dart menyediakan dua tipe “super” yang tampak serupa tetapi berperilaku sangat berbeda:

  • Object: supertype dari semua tipe non-nullable di Dart. Penggunaan Object tetap diperiksa oleh kompiler saat compile-time — hanya method yang ada di class Object yang bisa dipanggil tanpa cast. Ini memberikan keamanan tipe penuh.
  • dynamic: menonaktifkan seluruh pemeriksaan tipe Dart. Operasi apapun diperbolehkan oleh kompiler, tetapi error tipe muncul saat runtime (bukan compile-time). dynamic ibarat “lepas sabuk pengaman” — berbahaya jika tidak digunakan dengan hati-hati.

Prinsipnya: gunakan Object (atau generics) kapanpun memungkinkan; gunakan dynamic hanya saat benar-benar diperlukan (misalnya saat berinteraksi dengan data JSON yang belum diketahui strukturnya).

Pengecekan dan Konversi Tipe

Dart menyediakan beberapa mekanisme untuk bekerja dengan tipe secara aman:

  • is / is!: operator pengecekan tipe yang mengembalikan bool. Setelah is check, Dart secara otomatis mempromosikan tipe variabel (type promotion) dalam blok tersebut.
  • as: type cast eksplisit. Akan melempar TypeError saat runtime jika objek bukan tipe yang dimaksud — gunakan hanya jika yakin tipenya.
  • int.parse(String): mengkonversi String ke int. Melempar FormatException jika string tidak valid.
  • .toString(): mengkonversi nilai apa pun ke representasi String-nya.
  • .toInt() / .toDouble(): konversi antar tipe numerik (tersedia di num, int, double).

Null-Aware Operators

Sound null safety di Dart didukung oleh sekumpulan operator null-aware yang membuat penanganan nilai null lebih ringkas dan aman:

OperatorNamaPerilaku
?.Null-conditionalobj?.method() — panggil method() hanya jika obj tidak null; kembalikan null jika obj null
??Null-coalescingexpr ?? default — kembalikan expr jika tidak null, atau default jika null
??=Null-aware assignmentx ??= value — assign value ke x hanya jika x saat ini null
!Null assertionobj! — beritahu kompiler bahwa obj pasti tidak null; melempar Null check operator used on a null value saat runtime jika salah

Kata Kunci late

late memungkinkan deklarasi variabel non-nullable yang diinisialisasi setelah deklarasi, bukan saat deklarasi. Ini mengatasi dua masalah umum:

  1. Inisialisasi yang mahal (lazy initialization): variabel late tidak diinisialisasi sampai pertama kali diakses, menghemat sumber daya jika variabel mungkin tidak pernah digunakan.
  2. Field yang diinisialisasi di luar konstruktor: misalnya field yang baru bisa diset setelah super() selesai, atau dalam callback initState() di Flutter.

Jika variabel late diakses sebelum diinisialisasi, Dart melempar LateInitializationError saat runtime.

Sound Null Safety: Manfaat dan Cara Kerja

Sejak Dart 3.0, sound null safety adalah perilaku bawaan yang tidak bisa dinonaktifkan. Semua variabel secara default tidak bisa bernilai null — untuk variabel nullable, harus ditambahkan sufiks ? pada tipenya (contoh: String?, int?).

Manfaat utama sound null safety:

  • Eliminasi null checks di runtime: kompiler tahu variabel non-nullable tidak akan pernah null, sehingga tidak perlu menyisipkan pemeriksaan null saat runtime, menghasilkan kode yang lebih efisien.
  • Optimasi caching: karena kompiler dapat membuktikan nilai tertentu tidak akan pernah null, ia bisa mengoptimalkan bagaimana nilai tersebut disimpan dan diakses.
  • Bug terdeteksi lebih awal: NullPointerException yang biasanya hanya muncul saat runtime kini menjadi error kompiler yang muncul saat pengembangan.

Summary

final dan const berbeda fundamental: final hanya mengunci referensi (shallow) dievaluasi saat runtime, sedangkan const mengunci seluruh objek (deep, compile-time) dan memungkinkan canonicalization (satu instans di memori untuk nilai identik). Object mempertahankan keamanan tipe compile-time; dynamic menonaktifkannya dan berisiko error runtime. Sound null safety menjadikan semua variabel non-nullable secara default — tipe nullable ditandai dengan ?. Operator ?. (akses aman), ?? (nilai default), ??= (assign jika null), dan ! (null assertion) memperlancar penanganan null. Kata kunci late mendukung lazy initialization dan inisialisasi di luar konstruktor. Secara keseluruhan, null safety menggeser deteksi bug dari runtime ke compile-time dan memungkinkan optimasi performa oleh kompiler.