Strings

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 StringBuffer untuk efisiensi dalam loop.
  • length menghitung code unit UTF-16, bukan karakter visual. Untuk karakter Unicode di luar BMP (emoji, banyak karakter Asia), gunakan runes.length atau package characters.
  • 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.
  • StringBuffer untuk 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.
  • replaceAllMapped untuk transformasi dinamis — ketika teks pengganti bergantung pada konten yang cocok, bukan string statis.
  • utf8.encode/utf8.decode dari dart:convert untuk konversi string ke bytes — wajib saat bekerja dengan file, jaringan, dan API.

← Sebelumnya: Artikel & Sumber Daya   Berikutnya: IO →

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