Strings #
String di Dart adalah urutan kode UTF-16 yang immutable — setiap operasi yang tampak “mengubah” string sebenarnya menghasilkan objek String baru. Library bawaan Dart menyediakan puluhan method untuk memanipulasi string: pencarian, penggantian, pemisahan, transformasi case, padding, encoding, dan banyak lagi. Memahami method-method ini dan kapan masing-masing paling tepat digunakan adalah keterampilan sehari-hari dalam hampir setiap program Dart.
Membuat String #
// Literal — tanda kutip tunggal atau ganda, keduanya ekuivalen
String s1 = 'Halo, Dunia!';
String s2 = "Halo, Dunia!";
// Multi-line — triple kutip
String multiline = '''
Baris pertama
Baris kedua
Baris ketiga
''';
// Raw string — backslash tidak diinterpretasikan sebagai escape
String path = r'C:\Users\budi\Documents'; // \U dan \b bukan escape sequence
String regex = r'\d+\.\d{2}'; // lebih mudah dibaca untuk regex
// String interpolasi
String nama = 'Budi';
int umur = 25;
print('Nama: $nama, Umur: $umur tahun'); // variabel sederhana
print('Tahun lahir: ${2024 - umur}'); // ekspresi dalam ${}
print('Uppercase: ${nama.toUpperCase()}'); // method call dalam ${}
// Concatenation — gunakan interpolasi, bukan +
String gabung = 'Halo' + ', ' + 'Dunia'; // ok tapi verbose
String lebihBaik = 'Halo, Dunia'; // langsung
String dinamis = 'Halo, $nama!'; // paling idiomatis
Properti Dasar #
String teks = 'Pemrograman Dart';
// Panjang dalam code unit (UTF-16), bukan jumlah karakter visual
print(teks.length); // 16
// Cek string kosong
print(teks.isEmpty); // false
print(teks.isNotEmpty); // true
print(''.isEmpty); // true
// Akses karakter
print(teks[0]); // 'P' — karakter pertama
print(teks[teks.length - 1]); // 't' — karakter terakhir
// Akses code unit (integer nilai UTF-16)
print(teks.codeUnitAt(0)); // 80 (kode ASCII untuk 'P')
print(teks.codeUnits); // [80, 101, 109, ...] — semua code units
// Runes — untuk karakter Unicode di luar BMP
String emoji = '👋🌍';
print(emoji.length); // 4 (karena emoji = 2 code unit UTF-16 masing-masing)
print(emoji.runes.length); // 2 (karakter Unicode sesungguhnya)
for (final rune in emoji.runes) {
print(String.fromCharCode(rune)); // '👋', '🌍'
}
Pencarian dan Pencocokan #
String teks = 'Belajar pemrograman Dart dengan Dart';
// Mengandung substring
print(teks.contains('Dart')); // true
print(teks.contains('Python')); // false
print(teks.contains(RegExp(r'\d+'))); // false — tidak ada angka
// Dimulai/diakhiri dengan
print(teks.startsWith('Belajar')); // true
print(teks.endsWith('Dart')); // true
print(teks.startsWith('dart')); // false — case sensitive!
// Posisi kemunculan
print(teks.indexOf('Dart')); // 20 — kemunculan pertama
print(teks.lastIndexOf('Dart')); // 32 — kemunculan terakhir
print(teks.indexOf('Python')); // -1 — tidak ditemukan
// indexOf dengan posisi mulai
print(teks.indexOf('Dart', 21)); // 32 — cari setelah indeks 21
// Cek apakah string cocok dengan pola
final emailRegex = RegExp(r'^[\w\.-]+@[\w\.-]+\.\w{2,}$');
print(emailRegex.hasMatch('[email protected]')); // true
print(emailRegex.hasMatch('bukan-email')); // false
Transformasi String #
Case #
String campur = 'hElLo WoRlD';
print(campur.toUpperCase()); // 'HELLO WORLD'
print(campur.toLowerCase()); // 'hello world'
// Title case — tidak ada bawaan, perlu implementasi manual
String titleCase(String s) {
return s.split(' ')
.map((kata) => kata.isEmpty
? kata
: '${kata[0].toUpperCase()}${kata.substring(1).toLowerCase()}')
.join(' ');
}
print(titleCase('pemrograman dart')); // 'Pemrograman Dart'
Trim — Hapus Whitespace #
String kotor = ' spasi di mana-mana \n\t';
print(kotor.trim()); // 'spasi di mana-mana' — trim kiri dan kanan
print(kotor.trimLeft()); // 'spasi di mana-mana \n\t' — trim kiri saja
print(kotor.trimRight()); // ' spasi di mana-mana' — trim kanan saja
// Trim karakter kustom tidak didukung secara native — gunakan replaceAll
String dolar = '$$harga$$';
String bersih = dolar.replaceAll(RegExp(r'^\$+|\$+$'), ''); // 'harga'
Replace #
String teks = 'Dart adalah bahasa yang bagus. Dart sangat cepat.';
// Ganti semua kemunculan
print(teks.replaceAll('Dart', 'Kotlin'));
// 'Kotlin adalah bahasa yang bagus. Kotlin sangat cepat.'
// Ganti kemunculan pertama saja
print(teks.replaceFirst('Dart', 'Flutter'));
// 'Flutter adalah bahasa yang bagus. Dart sangat cepat.'
// Ganti dengan regex
String camelCase = 'namaDepanPengguna';
String snakeCase = camelCase.replaceAllMapped(
RegExp(r'[A-Z]'),
(m) => '_${m.group(0)!.toLowerCase()}',
); // 'nama_depan_pengguna'
// replaceRange — ganti substring berdasarkan indeks
String s = 'Halo, Dunia!';
print(s.replaceRange(6, 11, 'Flutter')); // 'Halo, Flutter!'
Split dan Join #
String csv = 'Jakarta,Bandung,Surabaya,Medan';
// Split berdasarkan delimiter
List<String> kota = csv.split(',');
print(kota); // ['Jakarta', 'Bandung', 'Surabaya', 'Medan']
// Split dengan regex
String teks = 'kata1 kata2 kata3';
List<String> kata = teks.split(RegExp(r'\s+'));
print(kata); // ['kata1', 'kata2', 'kata3']
// Split ke karakter individual
List<String> huruf = 'Dart'.split('');
print(huruf); // ['D', 'a', 'r', 't']
// Split dengan batasan jumlah bagian
// Tidak ada bawaan — implementasi manual
List<String> splitN(String s, String sep, int n) {
final parts = s.split(sep);
if (parts.length <= n) return parts;
return [
...parts.sublist(0, n - 1),
parts.sublist(n - 1).join(sep),
];
}
// Join — gabungkan list menjadi string
List<String> buah = ['apel', 'jeruk', 'mangga'];
print(buah.join(', ')); // 'apel, jeruk, mangga'
print(buah.join(' - ')); // 'apel - jeruk - mangga'
print(buah.join()); // 'apeljerukmanggga' — tanpa separator
Substring dan Slice #
String teks = 'Pemrograman Dart';
// substring(start) — dari indeks hingga akhir
print(teks.substring(12)); // 'Dart'
// substring(start, end) — dari start hingga end (eksklusif)
print(teks.substring(0, 11)); // 'Pemrograman'
print(teks.substring(12, 16)); // 'Dart'
// Karakter pertama dan terakhir
print(teks[0]); // 'P'
print(teks[teks.length - 1]); // 't'
// Ekstrak berdasarkan pencarian
final idx = teks.indexOf(' ');
final kata1 = teks.substring(0, idx); // 'Pemrograman'
final kata2 = teks.substring(idx + 1); // 'Dart'
Padding #
// padLeft — tambah karakter di kiri hingga panjang tertentu
print('42'.padLeft(5)); // ' 42' — padding dengan spasi
print('42'.padLeft(5, '0')); // '00042' — padding dengan '0'
print('Dart'.padLeft(8, '-')); // '----Dart'
// padRight — tambah karakter di kanan
print('Dart'.padRight(8)); // 'Dart '
print('Dart'.padRight(8, '.')); // 'Dart....'
// Penggunaan umum: format angka dengan lebar tetap
for (int i = 1; i <= 10; i++) {
print('${i.toString().padLeft(2)}: item');
// ' 1: item'
// ' 2: item'
// '10: item'
}
// Format jam digital
int jam = 9, menit = 5, detik = 3;
print('${'$jam'.padLeft(2, '0')}:${'$menit'.padLeft(2, '0')}:${'$detik'.padLeft(2, '0')}');
// '09:05:03'
Encoding dan Decoding #
import 'dart:convert';
// String ke bytes dan sebaliknya
String teks = 'Halo, Dunia! 🌍';
// UTF-8 encoding — standard untuk web dan file
List<int> utf8Bytes = utf8.encode(teks);
print(utf8Bytes.length); // lebih besar dari teks.length karena emoji = 4 bytes
String decoded = utf8.decode(utf8Bytes);
print(decoded); // 'Halo, Dunia! 🌍'
// Latin-1 / ISO-8859-1
List<int> latin1Bytes = latin1.encode('Halo'); // hanya karakter ASCII
String latin1Decoded = latin1.decode([72, 97, 108, 111]); // 'Halo'
// Base64
String original = 'Data rahasia: 12345';
String base64Encoded = base64Encode(utf8.encode(original));
print(base64Encoded); // 'RGF0YSByYWhhc2lhOiAxMjM0NQ=='
String base64Decoded = utf8.decode(base64Decode(base64Encoded));
print(base64Decoded); // 'Data rahasia: 12345'
// URL encoding
String url = 'https://example.com/pencarian?q=dart programming&lang=id';
String encoded = Uri.encodeFull(url);
print(encoded); // URL dengan karakter khusus di-encode
String queryParam = 'dart & flutter';
String paramEncoded = Uri.encodeComponent(queryParam);
print(paramEncoded); // 'dart%20%26%20flutter'
print(Uri.decodeComponent(paramEncoded)); // 'dart & flutter'
StringBuffer — Membangun String Secara Efisien
#
Untuk membangun string dari banyak bagian secara bertahap, StringBuffer jauh lebih efisien dari concatenation + berulang:
// ANTI-PATTERN: concatenation dalam loop — O(n²) memory
String hasilBuruk = '';
for (int i = 0; i < 10000; i++) {
hasilBuruk += 'item $i\n'; // ✗ buat string baru setiap iterasi
}
// BENAR: StringBuffer — O(n) memory
final buffer = StringBuffer();
for (int i = 0; i < 10000; i++) {
buffer.write('item $i');
buffer.writeln(); // tambah newline
}
final hasilBaik = buffer.toString(); // ✓ hanya materialisasi satu kali
// Method StringBuffer
final sb = StringBuffer();
sb.write('Halo'); // tambah string tanpa newline
sb.writeln(', Dunia!'); // tambah string dengan newline
sb.writeAll(['a', 'b', 'c'], ', '); // gabungkan list dengan separator
sb.writeCharCode(33); // tambah karakter dari code point ('!')
sb.clear(); // reset buffer
print(sb.length); // panjang akumulasi saat ini
print(sb.isEmpty); // true setelah clear
Komparasi String #
// Equality — case sensitive
print('dart' == 'dart'); // true
print('Dart' == 'dart'); // false
// compareTo — leksikografis (berdasarkan urutan Unicode)
print('a'.compareTo('b')); // -1 (a sebelum b)
print('b'.compareTo('a')); // 1 (b setelah a)
print('a'.compareTo('a')); // 0 (sama)
// Case-insensitive comparison
bool samaIgnoreCase(String a, String b) =>
a.toLowerCase() == b.toLowerCase();
print(samaIgnoreCase('Dart', 'dart')); // true
print(samaIgnoreCase('FLUTTER', 'flutter')); // true
// Sorting list string
List<String> bahasa = ['Kotlin', 'dart', 'Python', 'go'];
bahasa.sort(); // sort leksikografis — huruf kapital sebelum kecil
print(bahasa); // ['Kotlin', 'Python', 'dart', 'go']
// Sort case-insensitive
bahasa.sort((a, b) => a.toLowerCase().compareTo(b.toLowerCase()));
print(bahasa); // ['dart', 'go', 'Kotlin', 'Python']
Unicode dan Runes #
Dart string menggunakan UTF-16 — karakter di luar BMP (Basic Multilingual Plane) menggunakan dua code unit (surrogate pair). Untuk bekerja dengan karakter Unicode dengan benar:
// String dengan emoji
String s = 'Dart 🎯';
// length menghitung code unit UTF-16, bukan karakter visual
print(s.length); // 7 (5 karakter ASCII + 2 code unit untuk emoji)
// runes menghitung code point Unicode yang sesungguhnya
print(s.runes.length); // 6 (5 huruf + 1 emoji)
// Iterasi per karakter Unicode (bukan per code unit)
for (final rune in s.runes) {
final karakter = String.fromCharCode(rune);
print('$karakter (U+${rune.toRadixString(16).toUpperCase()})');
}
// Membuat string dari code point
print(String.fromCharCode(9786)); // '☺' (U+263A)
print(String.fromCharCodes([72, 97, 108, 111])); // 'Halo'
// Characters package — untuk grapheme cluster yang benar
// dart pub add characters
import 'package:characters/characters.dart';
final characters = '🇮🇩 Dart'.characters; // flag emoji = 2 code point tapi 1 grapheme
print(characters.length); // 7 grapheme cluster
Method Lengkap — Referensi Cepat #
String s = ' Halo, Dart! ';
// Informasi
s.length // panjang dalam code unit
s.isEmpty // apakah kosong
s.isNotEmpty // apakah tidak kosong
s.codeUnitAt(0) // code unit di indeks tertentu
s.codeUnits // semua code unit
s.runes // semua code point Unicode
// Pencarian
s.contains('Dart') // apakah mengandung substring
s.startsWith(' Halo') // apakah dimulai dengan
s.endsWith('! ') // apakah diakhiri dengan
s.indexOf('Dart') // posisi kemunculan pertama (-1 jika tidak ada)
s.lastIndexOf('a') // posisi kemunculan terakhir
// Transformasi
s.trim() // hapus whitespace kiri dan kanan
s.trimLeft() // hapus whitespace kiri
s.trimRight() // hapus whitespace kanan
s.toUpperCase() // huruf kapital semua
s.toLowerCase() // huruf kecil semua
s.padLeft(20) // padding kiri dengan spasi
s.padLeft(20, '0') // padding kiri dengan karakter tertentu
s.padRight(20) // padding kanan
// Ekstraksi
s.substring(2) // substring dari indeks
s.substring(2, 6) // substring rentang
s.split(',') // pecah berdasarkan delimiter
s[0] // akses karakter (code unit)
// Penggantian
s.replaceAll('Dart', 'Kotlin') // ganti semua
s.replaceFirst('Dart', 'Kotlin') // ganti pertama
s.replaceRange(2, 6, 'Baru') // ganti rentang indeks
s.replaceAllMapped(regex, (m) => ...) // ganti dengan fungsi
Anti-Pattern String #
Concatenation dalam Loop #
// ANTI-PATTERN: O(n²) karena setiap + membuat objek baru
String hasil = '';
for (final item in daftarPanjang) {
hasil += item + ', '; // ✗ sangat lambat untuk list besar
}
// BENAR: join untuk list
String hasil = daftarPanjang.join(', '); // ✓ O(n)
// BENAR: StringBuffer jika butuh logika lebih kompleks
final buffer = StringBuffer();
for (int i = 0; i < daftarPanjang.length; i++) {
buffer.write(daftarPanjang[i]);
if (i < daftarPanjang.length - 1) buffer.write(', ');
}
String hasil = buffer.toString(); // ✓ O(n)
Perbandingan String Case-Sensitive yang Tidak Disengaja #
// ANTI-PATTERN: membandingkan input user secara case sensitive
String input = ambilInputUser(); // 'ADMIN'
if (input == 'admin') { // ✗ tidak akan cocok
ijinkanAkses();
}
// BENAR: normalkan case sebelum bandingkan
if (input.toLowerCase() == 'admin') { // ✓
ijinkanAkses();
}
Ringkasan #
- String Dart immutable — setiap operasi transformasi menghasilkan String baru, bukan mengubah yang ada. Ini aman tapi perlu
StringBufferuntuk efisiensi dalam loop.lengthmenghitung code unit UTF-16, bukan karakter visual. Untuk karakter Unicode di luar BMP (emoji, banyak karakter Asia), gunakanrunes.lengthatau packagecharacters.- Raw string
r'...'sangat berguna untuk regex dan path file — backslash tidak diinterpretasikan sebagai escape sequence.- Interpolasi string
'$variabel'dan'${ekspresi}'lebih idiomatis dari concatenation+. Gunakan+hanya untuk menggabungkan dua string literal.StringBufferuntuk membangun string dalam loop — jauh lebih efisien dari concatenation+=yang menjadi O(n²) untuk string besar.split()+join()untuk transformasi list-string — lebih bersih dari loop manual.padLeft(n, '0')untuk memformat angka dengan lebar tetap — sangat berguna untuk jam, tanggal, dan nomor urut.toUpperCase()/toLowerCase()sebelum membandingkan string dari input user — jangan asumsi kapitalisasi yang konsisten.replaceAllMappeduntuk transformasi dinamis — ketika teks pengganti bergantung pada konten yang cocok, bukan string statis.utf8.encode/utf8.decodedaridart:convertuntuk konversi string ke bytes — wajib saat bekerja dengan file, jaringan, dan API.