Strings #
String in Dart is an immutable sequence of UTF-16 code units — every operation that appears to “modify” a string actually produces a new String object. Dart’s built-in library provides dozens of methods for string manipulation: searching, replacing, splitting, case transformations, padding, encoding, and more. Understanding these methods and when each is best used is a day-to-day skill in almost every Dart program.
Creating Strings #
// Literals — single or double quotes, both equivalent
String s1 = 'Hello, World!';
String s2 = "Hello, World!";
// Multi-line — triple quotes
String multiline = '''
First line
Second line
Third line
''';
// Raw strings — backslashes aren't interpreted as escapes
String path = r'C:\Users\budi\Documents'; // \U and \b aren't escape sequences
String regex = r'\d+\.\d{2}'; // easier to read for regex
// String interpolation
String nama = 'Budi';
int umur = 25;
print('Name: $nama, Age: $umur years'); // simple variables
print('Birth year: ${2024 - umur}'); // expressions in ${}
print('Uppercase: ${nama.toUpperCase()}'); // method calls in ${}
// Concatenation — use interpolation, not +
String gabung = 'Hello' + ', ' + 'World'; // okay but verbose
String lebihBaik = 'Hello, World'; // direct
String dinamis = 'Hello, $nama!'; // most idiomatic
Basic Properties #
String teks = 'Pemrograman Dart';
// Length in code units (UTF-16), not visual character count
print(teks.length); // 16
// Check for empty strings
print(teks.isEmpty); // false
print(teks.isNotEmpty); // true
print(''.isEmpty); // true
// Character access
print(teks[0]); // 'P' — first character
print(teks[teks.length - 1]); // 't' — last character
// Code unit access (UTF-16 integer value)
print(teks.codeUnitAt(0)); // 80 (ASCII code for 'P')
print(teks.codeUnits); // [80, 101, 109, ...] — all code units
// Runes — for Unicode characters beyond the BMP
String emoji = '👋🌍';
print(emoji.length); // 4 (because each emoji = 2 UTF-16 code units)
print(emoji.runes.length); // 2 (actual Unicode characters)
for (final rune in emoji.runes) {
print(String.fromCharCode(rune)); // '👋', '🌍'
}
Searching and Matching #
String teks = 'Belajar pemrograman Dart dengan Dart';
// Contains a substring
print(teks.contains('Dart')); // true
print(teks.contains('Python')); // false
print(teks.contains(RegExp(r'\d+'))); // false — no digits
// Starts/ends with
print(teks.startsWith('Belajar')); // true
print(teks.endsWith('Dart')); // true
print(teks.startsWith('dart')); // false — case sensitive!
// Occurrence positions
print(teks.indexOf('Dart')); // 20 — first occurrence
print(teks.lastIndexOf('Dart')); // 32 — last occurrence
print(teks.indexOf('Python')); // -1 — not found
// indexOf with a starting position
print(teks.indexOf('Dart', 21)); // 32 — search after index 21
// Check whether a string matches a pattern
final emailRegex = RegExp(r'^[\w\.-]+@[\w\.-]+\.\w{2,}$');
print(emailRegex.hasMatch('[email protected]')); // true
print(emailRegex.hasMatch('bukan-email')); // false
String Transformations #
Case #
String campur = 'hElLo WoRlD';
print(campur.toUpperCase()); // 'HELLO WORLD'
print(campur.toLowerCase()); // 'hello world'
// Title case — no built-in, needs a manual implementation
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 — Remove Whitespace #
String kotor = ' spasi di mana-mana \n\t';
print(kotor.trim()); // 'spasi di mana-mana' — trim both sides
print(kotor.trimLeft()); // 'spasi di mana-mana \n\t' — trim left only
print(kotor.trimRight()); // ' spasi di mana-mana' — trim right only
// Custom character trimming isn't natively supported — use replaceAll
String dolar = '$$harga$$';
String bersih = dolar.replaceAll(RegExp(r'^\$+|\$+$'), ''); // 'harga'
Replace #
String teks = 'Dart adalah bahasa yang bagus. Dart sangat cepat.';
// Replace all occurrences
print(teks.replaceAll('Dart', 'Kotlin'));
// 'Kotlin adalah bahasa yang bagus. Kotlin sangat cepat.'
// Replace only the first occurrence
print(teks.replaceFirst('Dart', 'Flutter'));
// 'Flutter adalah bahasa yang bagus. Dart sangat cepat.'
// Replace with a regex
String camelCase = 'namaDepanPengguna';
String snakeCase = camelCase.replaceAllMapped(
RegExp(r'[A-Z]'),
(m) => '_${m.group(0)!.toLowerCase()}',
); // 'nama_depan_pengguna'
// replaceRange — replace a substring by index
String s = 'Halo, Dunia!';
print(s.replaceRange(6, 11, 'Flutter')); // 'Halo, Flutter!'
Split and Join #
String csv = 'Jakarta,Bandung,Surabaya,Medan';
// Split by a delimiter
List<String> kota = csv.split(',');
print(kota); // ['Jakarta', 'Bandung', 'Surabaya', 'Medan']
// Split with a regex
String teks = 'kata1 kata2 kata3';
List<String> kata = teks.split(RegExp(r'\s+'));
print(kata); // ['kata1', 'kata2', 'kata3']
// Split into individual characters
List<String> huruf = 'Dart'.split('');
print(huruf); // ['D', 'a', 'r', 't']
// Split with a limited number of parts
// No built-in — manual implementation
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 — combine a list into a string
List<String> buah = ['apel', 'jeruk', 'mangga'];
print(buah.join(', ')); // 'apel, jeruk, mangga'
print(buah.join(' - ')); // 'apel - jeruk - mangga'
print(buah.join()); // 'apeljerukmanggga' — no separator
Substring and Slicing #
String teks = 'Pemrograman Dart';
// substring(start) — from the index to the end
print(teks.substring(12)); // 'Dart'
// substring(start, end) — from start to end (exclusive)
print(teks.substring(0, 11)); // 'Pemrograman'
print(teks.substring(12, 16)); // 'Dart'
// First and last characters
print(teks[0]); // 'P'
print(teks[teks.length - 1]); // 't'
// Extract based on a search
final idx = teks.indexOf(' ');
final kata1 = teks.substring(0, idx); // 'Pemrograman'
final kata2 = teks.substring(idx + 1); // 'Dart'
Padding #
// padLeft — add characters to the left until a certain length
print('42'.padLeft(5)); // ' 42' — padding with spaces
print('42'.padLeft(5, '0')); // '00042' — padding with '0'
print('Dart'.padLeft(8, '-')); // '----Dart'
// padRight — add characters to the right
print('Dart'.padRight(8)); // 'Dart '
print('Dart'.padRight(8, '.')); // 'Dart....'
// Common use: format numbers with a fixed width
for (int i = 1; i <= 10; i++) {
print('${i.toString().padLeft(2)}: item');
// ' 1: item'
// ' 2: item'
// '10: item'
}
// Digital clock formatting
int jam = 9, menit = 5, detik = 3;
print('${'$jam'.padLeft(2, '0')}:${'$menit'.padLeft(2, '0')}:${'$detik'.padLeft(2, '0')}');
// '09:05:03'
Encoding and Decoding #
import 'dart:convert';
// String to bytes and back
String teks = 'Halo, Dunia! 🌍';
// UTF-8 encoding — the standard for web and files
List<int> utf8Bytes = utf8.encode(teks);
print(utf8Bytes.length); // larger than teks.length because the emoji = 4 bytes
String decoded = utf8.decode(utf8Bytes);
print(decoded); // 'Halo, Dunia! 🌍'
// Latin-1 / ISO-8859-1
List<int> latin1Bytes = latin1.encode('Halo'); // only ASCII characters
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 with special characters encoded
String queryParam = 'dart & flutter';
String paramEncoded = Uri.encodeComponent(queryParam);
print(paramEncoded); // 'dart%20%26%20flutter'
print(Uri.decodeComponent(paramEncoded)); // 'dart & flutter'
StringBuffer — Building Strings Efficiently
#
For building strings from many parts incrementally, StringBuffer is far more efficient than repeated + concatenation:
// ANTI-PATTERN: concatenation in a loop — O(n²) memory
String hasilBuruk = '';
for (int i = 0; i < 10000; i++) {
hasilBuruk += 'item $i\n'; // ✗ creates a new string every iteration
}
// CORRECT: StringBuffer — O(n) memory
final buffer = StringBuffer();
for (int i = 0; i < 10000; i++) {
buffer.write('item $i');
buffer.writeln(); // add a newline
}
final hasilBaik = buffer.toString(); // ✓ only materialized once
// StringBuffer methods
final sb = StringBuffer();
sb.write('Halo'); // add a string without a newline
sb.writeln(', Dunia!'); // add a string with a newline
sb.writeAll(['a', 'b', 'c'], ', '); // join a list with a separator
sb.writeCharCode(33); // add a character from a code point ('!')
sb.clear(); // reset the buffer
print(sb.length); // current accumulated length
print(sb.isEmpty); // true after clear
String Comparison #
// Equality — case sensitive
print('dart' == 'dart'); // true
print('Dart' == 'dart'); // false
// compareTo — lexicographic (based on Unicode order)
print('a'.compareTo('b')); // -1 (a before b)
print('b'.compareTo('a')); // 1 (b after a)
print('a'.compareTo('a')); // 0 (equal)
// Case-insensitive comparison
bool samaIgnoreCase(String a, String b) =>
a.toLowerCase() == b.toLowerCase();
print(samaIgnoreCase('Dart', 'dart')); // true
print(samaIgnoreCase('FLUTTER', 'flutter')); // true
// Sorting string lists
List<String> bahasa = ['Kotlin', 'dart', 'Python', 'go'];
bahasa.sort(); // lexicographic sort — uppercase before lowercase
print(bahasa); // ['Kotlin', 'Python', 'dart', 'go']
// Case-insensitive sort
bahasa.sort((a, b) => a.toLowerCase().compareTo(b.toLowerCase()));
print(bahasa); // ['dart', 'go', 'Kotlin', 'Python']
Unicode and Runes #
Dart strings use UTF-16 — characters beyond the BMP (Basic Multilingual Plane) use two code units (a surrogate pair). To work with Unicode characters correctly:
// A string with an emoji
String s = 'Dart 🎯';
// length counts UTF-16 code units, not visual characters
print(s.length); // 7 (5 ASCII characters + 2 code units for the emoji)
// runes counts the actual Unicode code points
print(s.runes.length); // 6 (5 letters + 1 emoji)
// Iterate per Unicode character (not per code unit)
for (final rune in s.runes) {
final karakter = String.fromCharCode(rune);
print('$karakter (U+${rune.toRadixString(16).toUpperCase()})');
}
// Create a string from a code point
print(String.fromCharCode(9786)); // '☺' (U+263A)
print(String.fromCharCodes([72, 97, 108, 111])); // 'Halo'
// The characters package — for proper grapheme clusters
// dart pub add characters
import 'package:characters/characters.dart';
final characters = '🇮🇩 Dart'.characters; // flag emoji = 2 code points but 1 grapheme
print(characters.length); // 7 grapheme clusters
Complete Method Reference — Quick Cheat Sheet #
String s = ' Halo, Dart! ';
// Information
s.length // length in code units
s.isEmpty // whether empty
s.isNotEmpty // whether not empty
s.codeUnitAt(0) // code unit at a specific index
s.codeUnits // all code units
s.runes // all Unicode code points
// Searching
s.contains('Dart') // whether it contains a substring
s.startsWith(' Halo') // whether it starts with
s.endsWith('! ') // whether it ends with
s.indexOf('Dart') // position of the first occurrence (-1 if absent)
s.lastIndexOf('a') // position of the last occurrence
// Transformations
s.trim() // remove left and right whitespace
s.trimLeft() // remove left whitespace
s.trimRight() // remove right whitespace
s.toUpperCase() // all uppercase
s.toLowerCase() // all lowercase
s.padLeft(20) // left padding with spaces
s.padLeft(20, '0') // left padding with a specific character
s.padRight(20) // right padding
// Extraction
s.substring(2) // substring from an index
s.substring(2, 6) // substring range
s.split(',') // split by a delimiter
s[0] // character access (code unit)
// Replacement
s.replaceAll('Dart', 'Kotlin') // replace all
s.replaceFirst('Dart', 'Kotlin') // replace the first
s.replaceRange(2, 6, 'Baru') // replace an index range
s.replaceAllMapped(regex, (m) => ...) // replace with a function
String Anti-Patterns #
Concatenation in a Loop #
// ANTI-PATTERN: O(n²) because every + creates a new object
String hasil = '';
for (final item in daftarPanjang) {
hasil += item + ', '; // ✗ very slow for large lists
}
// CORRECT: join for lists
String hasil = daftarPanjang.join(', '); // ✓ O(n)
// CORRECT: StringBuffer if you need more complex logic
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)
Accidental Case-Sensitive Comparisons #
// ANTI-PATTERN: comparing user input case-sensitively
String input = ambilInputUser(); // 'ADMIN'
if (input == 'admin') { // ✗ won't match
ijinkanAkses();
}
// CORRECT: normalize the case before comparing
if (input.toLowerCase() == 'admin') { // ✓
ijinkanAkses();
}
Summary #
- Dart strings are immutable — every transformation operation produces a new String, not a modification of the existing one. This is safe but requires
StringBufferfor efficiency in loops.lengthcounts UTF-16 code units, not visual characters. For Unicode characters beyond the BMP (emojis, many Asian characters), userunes.lengthor thecharacterspackage.- Raw strings
r'...'are very useful for regex and file paths — backslashes aren’t interpreted as escape sequences.- String interpolation
'$variable'and'${expression}'is more idiomatic than+concatenation. Use+only to join two string literals.StringBufferfor building strings in loops — far more efficient than+=concatenation, which becomes O(n²) for large strings.split()+join()for list-string transformations — cleaner than manual loops.padLeft(n, '0')for formatting numbers with a fixed width — very useful for clocks, dates, and sequence numbers.toUpperCase()/toLowerCase()before comparing strings from user input — don’t assume consistent capitalization.replaceAllMappedfor dynamic transformations — when the replacement text depends on the matched content, not a static string.utf8.encode/utf8.decodefromdart:convertfor converting strings to bytes — essential when working with files, networks, and APIs.