Pemrograman Dart
Memahami Sound Null Safety Sejak Awal
Pahami konsep Sound Null Safety di Dart 3: perbedaan tipe non-nullable dan nullable, operator null-aware (??, ??=, ?.), bahaya operator !, serta kata kunci late.

Salah satu penyebab aplikasi ponsel tiba-tiba berhenti paksa (force close) yang paling terkenal dalam sejarah rekayasa perangkat lunak adalah Null Reference Exception—kondisi ketika program mencoba memanggil properti atau melakukan operasi pada variabel yang ternyata kosong (null). Pencipta konsep referensi null, Tony Hoare, bahkan menyebutnya sebagai "kesalahan bernilai miliaran dolar". Untuk menghentikan masalah ini dari akarnya, Dart menerapkan sistem Sound Null Safety. Dengan fitur ini, kesalahan akibat nilai null tidak lagi mengintai saat aplikasi sudah digunakan pengguna, melainkan langsung tertangkap oleh editor kode sebelum program dijalankan.
Yang akan dipelajari
- Memahami mengapa nilai
nullberbahaya dan apa arti "Sound" pada Sound Null Safety di Dart 3. - Membedakan tipe data non-nullable (
String,int) dan nullable (String?,int?). - Menggunakan operator null-aware (
??,??=, dan?.) untuk mengolah nilai yang berpotensinullsecara aman. - Memahami cara kerja type promotion otomatis serta risiko penggunaan null assertion operator (
!). - Menggunakan kata kunci
lateuntuk variabel non-nullable yang baru bisa diinisialisasi belakangan.
Mengapa Null Safety Penting?
Bayangkan sebuah aplikasi belanja daring yang ingin menampilkan nama panggilan pengguna dalam huruf kapital menggunakan namaPanggilan.toUpperCase(). Bagaimana jika pengguna baru mendaftar dan belum mengisi kolom nama panggilan di profilnya? Jika variabel namaPanggilan berisi null (tidak merujuk ke objek String mana pun di memori), maka memanggil .toUpperCase() pada kehampaan akan membuat aplikasi langsung crash.
Di Dart 3, semua tipe data secara bawaan tidak boleh bernilai null (non-nullable by default). Kata Sound (utuh/kedap) berarti jaminan dari kompilator Dart bersifat mutlak: jika sebuah variabel bertipe String (tanpa tanda tanya), maka Dart menjamin 100% bahwa variabel tersebut tidak akan pernah berisi null selama program berjalan.
Perbandingan tipe Non-Nullable vs Nullable
Untuk mengizinkan sebuah variabel menampung nilai null, kita harus secara eksplisit menambahkan tanda tanya (?) di belakang nama tipe datanya.
| Deklarasi Tipe | Kategori | Boleh diisi null? |
Boleh langsung memanggil method (mis. .length)? |
|---|---|---|---|
String nama = 'Andi'; |
Non-nullable | Tidak (Error saat kompilasi) | Bisa langsung (nama.length dijamin aman) |
String? julukan; |
Nullable | Boleh (Otomatis bernilai null jika belum diisi) |
Tidak bisa langsung (Harus dicek atau memakai operator null-aware) |
Mari kita lihat perilaku keduanya di dalam kode Dart 3:
void main() {
// 1. Variabel Non-Nullable: wajib diisi nilai valid sebelum digunakan
String namaLengkap = 'Rizky Pratama';
// namaLengkap = null; // Jika baris ini diaktifkan, Dart akan menolak kompilasi!
// 2. Variabel Nullable: ditandai dengan '?' di akhir tipe data
String? bioSingkat; // Otomatis bernilai null karena belum diberi nilai awal
int? umurOpsional = null;
print('Nama Lengkap : $namaLengkap (${namaLengkap.length} karakter)');
print('Bio Singkat : $bioSingkat');
print('Umur : $umurOpsional');
// Mengisi nilai ke variabel nullable di kemudian waktu
bioSingkat = 'Mahasiswa Teknik Informatika';
print('Bio Baru : $bioSingkat');
}
// Output:
// Nama Lengkap : Rizky Pratama (13 karakter)
// Bio Singkat : null
// Umur : null
// Bio Baru : Mahasiswa Teknik Informatika
Operator Null-Aware (?., ??, dan ??=)
Karena variabel nullable (T?) berisiko berisi null, Dart melarang kita memanggil properti atau method secara langsung seperti bioSingkat.length. Sebagai gantinya, Dart menyediakan tiga operator null-aware yang ringkas dan elegan:
| Operator | Nama | Cara Kerja |
|---|---|---|
?. |
Conditional member access | Mengakses properti/method jika objek tidak null. Jika objek null, seluruh ekspresi langsung menghasilkan null tanpa error. |
?? |
If-null operator | Mengembalikan nilai di sebelah kiri jika tidak null; jika kiri null, gunakan nilai cadangan (fallback) di sebelah kanan. |
??= |
Null-aware assignment | Mengisi variabel di sebelah kiri hanya jika variabel tersebut saat ini masih bernilai null. |
Berikut contoh penggabungan ketiga operator tersebut dalam skenario nyata:
void main() {
String? namaPanggilan;
String? kotaAsal = 'Yogyakarta';
// 1. Operator ?. (akses aman)
// Karena namaPanggilan masih null, hasilnya null (tidak crash)
print('Panjang nama panggilan: ${namaPanggilan?.length}');
print('Kota dalam huruf besar: ${kotaAsal?.toUpperCase()}');
// 2. Operator ?? (memberikan nilai cadangan / default)
String sapaan = namaPanggilan ?? 'Pengguna Tamu';
int jumlahKarakter = namaPanggilan?.length ?? 0;
print('Halo, $sapaan! (Panjang nama: $jumlahKarakter)');
// 3. Operator ??= (mengisi variabel hanya jika masih null)
namaPanggilan ??= 'Sahabat Dart'; // Diisi karena namaPanggilan tadinya null
kotaAsal ??= 'Jakarta'; // Diabaikan karena kotaAsal sudah berisi 'Yogyakarta'
print('Nama Panggilan setelah ??= : $namaPanggilan');
print('Kota Asal setelah ??= : $kotaAsal');
}
// Output:
// Panjang nama panggilan: null
// Kota dalam huruf besar: YOGYAKARTA
// Halo, Pengguna Tamu! (Panjang nama: 0)
// Nama Panggilan setelah ??= : Sahabat Dart
// Kota Asal setelah ??= : Yogyakarta
Perhatikan betapa rapi kombinasi namaPanggilan?.length ?? 0. Ekspresi ini dibaca: "Ambil panjang karakter namaPanggilan jika ada, namun jika null, berikan angka 0." Hasil akhirnya bertipe int murni (non-nullable), bukan int?.
Type Promotion vs Null Assertion (!)
Cara paling aman untuk mengubah tipe nullable (String?) menjadi non-nullable (String) adalah memeriksa nilainya terlebih dahulu dengan percabangan if (variabel != null). Penganalisis kode Dart sangat cerdas: begitu melewati pemeriksaan tersebut, tipe variabel otomatis "dipromosikan" (type promotion) menjadi non-nullable.
Namun, terkadang pengembang tergoda mengambil jalan pintas menggunakan null assertion operator (!) atau sering dijuluki bang operator. Menambahkan tanda seru di belakang variabel nullable (variabel!) berarti kita berkata kepada kompilator: "Percayalah padaku, variabel ini pasti tidak null!"
Peringatan Penting: Jika ternyata variabel yang diberi tanda
!tersebut bernilainullsaat program berjalan, jaminan Sound Null Safety akan gugur dan program akan langsung meledak (runtime crash).
void main() {
String? kuponDiskon = 'HEMAT50';
// CARA TERBAIK: Pemeriksaan null memicu Type Promotion otomatis
if (kuponDiskon != null) {
// Di dalam blok ini, kuponDiskon otomatis diperlakukan sebagai String (bukan String?)
print('Kode kupon valid (${kuponDiskon.length} karakter): ${kuponDiskon.toLowerCase()}');
}
// CARA BERISIKO: Menggunakan operator ! (Null Assertion)
// Aman HANYA karena kita tahu pasti kuponDiskon sudah diisi 'HEMAT50'
String kuponPastiAda = kuponDiskon!;
print('Kupon dipaksa: $kuponPastiAda');
}
// Output:
// Kode kupon valid (7 karakter): hemat50
// Kupon dipaksa: HEMAT50
Aturan praktisnya sederhana: hindari operator ! sebisa mungkin. Selalu utamakan operator ??, ?., atau pemeriksaan if (x != null).
Kata Kunci late
Bagaimana jika kita memiliki variabel yang kita tahu pasti tidak akan pernah bernilai null saat dipakai, tetapi nilainya belum bisa kita isi pada baris saat variabel itu dideklarasikan? Di sinilah kata kunci late berperan.
Kata kunci late memiliki dua fungsi utama:
- Inisialisasi tertunda (Deferred initialization): Mengizinkan variabel non-nullable dideklarasikan tanpa nilai awal, dengan janji bahwa kita pasti mengisinya sebelum variabel tersebut dibaca.
- Evaluasi malas (Lazy initialization): Jika
latedipasangkan dengan pemanggilan fungsi inisialisasi, fungsi tersebut baru akan dijalankan pada saat variabel pertama kali diakses, bukan saat dideklarasikan.
String buatLaporanBerat() {
print('--> [Proses] Menghitung laporan tahunan...');
return 'Laporan Keuangan 2026: Laba Bersih Naik 18%';
}
void main() {
// 1. Variabel late non-nullable yang diisi belakangan
late String nomorIndukMahasiswa;
// Simulasi proses pendaftaran selesai
nomorIndukMahasiswa = '2026101045';
print('NIM Terdaftar: $nomorIndukMahasiswa');
// 2. Lazy initialization dengan late
late String isiLaporan = buatLaporanBerat();
print('Program berjalan, tetapi variabel isiLaporan belum dibaca.');
print('Sekarang kita baca isiLaporan:');
print(isiLaporan); // Fungsi buatLaporanBerat() baru dipanggil di baris ini!
}
// Output:
// NIM Terdaftar: 2026101045
// Program berjalan, tetapi variabel isiLaporan belum dibaca.
// Sekarang kita baca isiLaporan:
// --> [Proses] Menghitung laporan tahunan...
// Laporan Keuangan 2026: Laba Bersih Naik 18%
Sama seperti operator !, berhati-hatilah saat menggunakan late: jika Anda lupa mengisi variabel late namun sudah telanjur mencetak atau membacanya, Dart akan melempar kesalahan LateInitializationError saat program berjalan.
Coba sendiri
Buka dartpad.dev dan jalankan eksperimen singkat berikut untuk merasakan langsung perlindungan Sound Null Safety:
- Ketik kode berikut di dalam
main():
Klik Run dan amati pesan kesalahan Uncaught TypeError / Null check operator used on a null value yang muncul di konsol.String? pesanRahasia; print(pesanRahasia!.toUpperCase()); - Sekarang ubah baris kedua menjadi
print(pesanRahasia?.toUpperCase() ?? 'BELUM ADA PESAN');dan klik Run kembali. Perhatikan bagaimana program berjalan mulus tanpa error.
Kesalahan yang sering terjadi
| Kesalahan Pemula | Penyebab | Solusi yang Benar |
|---|---|---|
Menambahkan operator ! setiap kali editor memberi garis merah pada variabel String? |
Ingin menghilangkan pesan error kompilasi secara instan tanpa memikirkan kondisi saat data kosong. | Gunakan nilai cadangan variabel ?? 'Nilai Default' atau periksa dengan if (variabel != null). |
Membaca variabel late sebelum diberi nilai awal |
Mengira variabel late akan otomatis bernilai null atau string kosong jika belum diisi. |
Pastikan semua jalur kode mengisi variabel late sebelum dibaca, atau gunakan tipe nullable (String?) jika nilainya memang opsional. |
Menulis int? total = harga + diskon; ketika diskon bertipe int? |
Operator + tidak bisa menjumlahkan angka dengan nilai yang mungkin null. |
Berikan nilai default 0 pada variabel nullable: int total = harga + (diskon ?? 0);. |
Mengira String? teks = ''; (string kosong) sama dengan null |
Bingung antara "wadah berisi teks sepanjang 0 karakter" dan "tidak ada wadah sama sekali". | Ingat bahwa '' adalah objek String valid dengan .length bernilai 0, sedangkan null berarti ketiadaan nilai. |
Kaitannya dengan Flutter / Dunia Nyata: Saat aplikasi Flutter mengambil data profil pengguna dari API internet atau formulir input, banyak kolom bersifat opsional—seperti foto profil (
String? avatarUrl), alamat kedua (String? alamatTambahan), atau tanggal lahir (DateTime?). DenganavatarUrl ?? 'assets/default_avatar.png', aplikasi kita bisa langsung menampilkan gambar standar ketika pengguna belum mengunggah foto, tanpa takut layar menjadi merah (Red Screen of Death) akibat nilainull.
Latihan
Uji pemahaman Anda tentang Sound Null Safety dengan menyelesaikan tiga kasus berikut di DartPad:
- Latihan 1 (Profil Pengguna Opsional): Buatlah variabel
String username = 'andi_dev'(non-nullable) danString? namaTampilan(nullable, biarkan belum diisi/null). Cetak kalimat sapaan yang menampilkannamaTampilanjika ada, atau menampilkanusernamejikanamaTampilanbernilainullmenggunakan operator??. - Latihan 2 (Kasir dengan Kupon dan Ongkir Nullable): Diketahui
int totalBelanja = 150000;,int? potonganKupon = null;, danint? biayaOngkir = 12000;. Hitungint totalAkhirdengan rumustotalBelanja - potonganKupon + biayaOngkirsecara aman menggunakan operator??agar nilai yangnulldianggap0. - Latihan 3 (Inisialisasi
??=dan Akses Aman?.): Buat variabelString? catatanPengiriman;. Pertama, tampilkan panjang karaktercatatanPengirimansecara aman (jikanull, tampilkan angka0). Kedua, gunakan operator??=untuk mengisicatatanPengirimandengan'Titip di pos satpam'jika masih kosong. Ketiga, tampilkan kembali isi catatan dalam huruf kapital dan panjang karakternya.
Kunci jawaban dan pembahasan
Latihan 1
void main() {
String username = 'andi_dev';
String? namaTampilan;
String namaYangDitampilkan = namaTampilan ?? username;
print('Selamat datang kembali, $namaYangDitampilkan!');
// Jika pengguna kemudian mengatur nama tampilan:
namaTampilan = 'Andi Setiawan';
print('Selamat datang kembali, ${namaTampilan ?? username}!');
}
// Output:
// Selamat datang kembali, andi_dev!
// Selamat datang kembali, Andi Setiawan!
Pembahasan: Operator ?? memeriksa sisi kiri (namaTampilan). Pada cetakan pertama, nilainya masih null sehingga ekspresi mengambil nilai cadangan username ('andi_dev'). Pada cetakan kedua, karena namaTampilan sudah berisi 'Andi Setiawan', nilai itulah yang ditampilkan.
Latihan 2
void main() {
int totalBelanja = 150000;
int? potonganKupon = null;
int? biayaOngkir = 12000;
int totalAkhir = totalBelanja - (potonganKupon ?? 0) + (biayaOngkir ?? 0);
print('Total Belanja : Rp$totalBelanja');
print('Potongan Kupon : Rp${potonganKupon ?? 0}');
print('Biaya Ongkir : Rp${biayaOngkir ?? 0}');
print('Total Akhir : Rp$totalAkhir');
}
// Output:
// Total Belanja : Rp150000
// Potongan Kupon : Rp0
// Biaya Ongkir : Rp12000
// Total Akhir : Rp162000
Pembahasan: Di Dart, operasi aritmatika tidak dapat dilakukan langsung pada tipe int?. Dengan membungkus (potonganKupon ?? 0) dan (biayaOngkir ?? 0) di dalam tanda kurung, masing-masing ekspresi berubah menjadi int murni sebelum dijumlahkan atau dikurangkan.
Latihan 3
void main() {
String? catatanPengiriman;
// 1. Cek panjang karakter saat masih null
print('Panjang catatan awal : ${catatanPengiriman?.length ?? 0}');
// 2. Isi nilai default hanya jika catatanPengiriman masih null
catatanPengiriman ??= 'Titip di pos satpam';
// 3. Cetak huruf kapital dan panjang karakter terbaru
print('Catatan Pengiriman : ${catatanPengiriman.toUpperCase()}');
print('Panjang catatan baru : ${catatanPengiriman.length}');
}
// Output:
// Panjang catatan awal : 0
// Catatan Pengiriman : TITIP DI POS SATPAM
// Panjang catatan baru : 19
Pembahasan: Pada langkah pertama, kombinasi catatanPengiriman?.length ?? 0 mencegah error saat variabel masih null dan mengembalikan 0. Setelah baris catatanPengiriman ??= 'Titip di pos satpam'; dijalankan, kompilator Dart mengetahui bahwa catatanPengiriman kini pasti sudah terisi nilai non-null (type promotion), sehingga pada langkah ketiga kita bahkan dapat memanggil .toUpperCase() dan .length secara langsung tanpa tanda ?..
Discussion (0)
We store your optional name, your comment, and technical hashes (not your raw IP address) to prevent spam. A functional cookie lets you edit or delete your own comments from this browser. To request deletion, use the contact page.
No comments yet. Start the discussion.