| |

Dart 5 ๐ŸŽฏ String Manipulation, Interpolation, Multiline Literals, and Raw Strings

The previous chapter introduced String as one of Dart’s built-in types. This chapter goes deeper. A String is not just a container for text โ€” it is a class with dozens of methods for searching, splitting, replacing, trimming, and transforming text. It supports interpolation, which embeds expressions directly in the literal. It supports multiline literals for text that spans multiple lines. And it supports raw strings for cases where escape sequences must be treated literally.

String manipulation is one of the most common tasks in any programming language. Dart’s string API is rich but consistent. Understanding it is the difference between writing code that fights the language and code that uses it naturally.

Key point: Dart strings are immutable. Every method that appears to modify a string actually returns a new string. The original is never changed. This is not a limitation โ€” it is a design choice that makes strings safe to share, safe to use as map keys, and safe to pass across isolate boundaries. The cost is that building large strings by concatenation is slow, which is why StringBuffer exists.


Why String Manipulation Matters

Almost every application processes text. User input, API responses, log files, configuration values, HTML, JSON โ€” all of it is string data. The way you manipulate that data determines the clarity, correctness, and performance of your code.

The transformation problem. You receive a string and need to clean it: trim whitespace, change case, remove characters, replace patterns. Dart provides methods for each of these operations. Knowing which method to use avoids manual character-by-character loops.

The interpolation problem. Building strings by concatenation is verbose and error-prone. String interpolation embeds expressions directly in the literal. The compiler handles the conversion. The result is cleaner and easier to read.

The escape problem. Some strings contain characters that have special meaning: backslashes in Windows paths, dollar signs in shell scripts, newlines in JSON. Raw strings treat every character literally. They eliminate the need to escape every backslash.

The performance problem. Dart strings are immutable. Concatenating in a loop creates a new string on every iteration. StringBuffer accumulates content in a buffer and produces the final string once. For any loop that builds a string from many pieces, StringBuffer is the correct tool.

The trade-off. Dart’s string API is comprehensive, but not every method is needed for every task. The most common methods โ€” length, isEmpty, contains, split, trim, replaceAll, toUpperCase, toLowerCase, substring, indexOf โ€” cover most cases. The rest are there when the task requires them.


a. String Interpolation and Expression Embedding

String interpolation embeds an expression inside a string. The expression is evaluated, converted to a string if necessary, and inserted at that position.

var name = 'Alice';
var age = 30;

var greeting = 'Hello, $name!';              // Hello, Alice!
var info = 'You are $age years old.';        // You are 30 years old.

The $identifier syntax works for simple identifiers. For more complex expressions, use ${expression}:

var name = 'Alice';
var message = 'Hello, ${name.toUpperCase()}!';       // Hello, ALICE!
var math = 'The sum is ${2 + 3}.';                   // The sum is 5.
var nested = 'Nested: ${'inner $name'}';             // Nested: inner Alice

The braces delimit the expression. Anything inside is evaluated as Dart code. The result is converted to a string with toString().

When the expression is a simple identifier, the braces are optional. $name and ${name} are equivalent. But when the identifier is adjacent to other characters that could be mistaken for part of the name, the braces are required:

var file = 'report';
var bad = '$fileport';         // Looks for a variable named "fileport"
var good = '${file}port';      // reportport

The compiler treats $fileport as a reference to a variable called fileport. The braces separate the variable name from the following text.

String interpolation works with any expression, including method calls, arithmetic, and nested interpolation:

var price = 19.99;
var quantity = 3;
var total = 'Total: \$${(price * quantity).toStringAsFixed(2)}';
// Total: $59.97

The \$ escape is needed because the dollar sign is the interpolation character. The toStringAsFixed(2) formats the number with two decimal places.


b. Multiline Literals and Raw Strings

A multiline string spans multiple lines in the source file. Dart provides two ways to write them: triple single quotes and triple double quotes.

var text1 = '''
This is a
multiline string
using triple single quotes.
''';

var text2 = """
This is a
multiline string
using triple double quotes.
""";

Both forms produce identical results. The choice is stylistic. The triple-quoted string preserves everything between the quotes, including newlines and indentation.

The trim() method is commonly used with multiline strings to remove the leading and trailing whitespace that comes from indentation in the source:

var text = '''
    Line one
    Line two
    Line three
'''.trim();

print(text);
// Line one
// Line two
// Line three

Without trim(), the string would start with a newline and the first line of content would be indented by four spaces.

A raw string is prefixed with r. Inside a raw string, backslashes are literal characters. They do not introduce escape sequences.

var normal = 'Line 1\nLine 2';        // Contains a newline
var raw = r'Line 1\nLine 2';          // Contains backslash-n literally

print(normal);
// Line 1
// Line 2

print(raw);
// Line 1\nLine 2

Raw strings are essential for regular expressions and file paths, where backslashes have specific meanings that should not be interpreted by the string parser:

var pattern = r'\d{3}-\d{4}';          // Regex: three digits, hyphen, four digits
var windowsPath = r'C:\Users\Alice\Documents';

Without the r prefix, \d would be interpreted as an escape sequence (which does not exist in Dart, so it would be an error), and \U in the Windows path would also be an error.

A raw string cannot end with a backslash. The backslash would escape the closing quote, and the string would not terminate. The workaround is to use adjacent literals:

// var bad = r'C:\';                    // โŒ Error: unterminated string
var good = r'C:\' '\';                  // โœ… "C:\"

Raw strings can be multiline. The r prefix and the triple quotes combine:

var raw = r'''
Line 1\nLine 2
Line 3
''';

The \n is literal. The newlines between the triple quotes are actual newlines.


c. Common String Methods

Dart’s String class provides a rich API. The most commonly used methods fall into a few categories.

Length and emptiness. The length property returns the number of UTF-16 code units. The isEmpty and isNotEmpty properties are booleans.

var s = 'hello';
print(s.length);        // 5
print(s.isEmpty);       // false
print(''.isEmpty);      // true

The length counts UTF-16 code units, not characters. A character outside the Basic Multilingual Plane (such as an emoji) is represented by two code units, so length may be larger than the visible character count.

Searching. The contains method checks whether a string contains another string. The startsWith and endsWith methods check the beginning and end. The indexOf and lastIndexOf methods return the position of a substring, or -1 if not found.

var s = 'Hello, world!';
print(s.contains('world'));       // true
print(s.startsWith('Hello'));     // true
print(s.endsWith('!'));           // true
print(s.indexOf('o'));            // 4
print(s.lastIndexOf('o'));        // 8
print(s.indexOf('xyz'));          // -1

Case conversion. The toUpperCase and toLowerCase methods return new strings with the case changed.

var s = 'Hello';
print(s.toUpperCase());    // HELLO
print(s.toLowerCase());    // hello

Trimming. The trim, trimLeft, and trimRight methods remove whitespace from the ends of a string.

var s = '   Hello   ';
print(s.trim());           // 'Hello'
print(s.trimLeft());       // 'Hello   '
print(s.trimRight());      // '   Hello'

Splitting and joining. The split method divides a string into a list of substrings based on a delimiter. The join method on a list combines the elements into a single string.

var s = 'one,two,three';
var parts = s.split(',');
print(parts);              // [one, two, three]

var joined = parts.join(' | ');
print(joined);             // one | two | three

The split method accepts a Pattern, which can be a string or a regular expression. The splitMapJoin method combines splitting, transforming, and joining in a single operation.

Replacing. The replaceAll and replaceFirst methods replace occurrences of a pattern with a replacement string.

var s = 'Hello, world!';
print(s.replaceAll('o', '0'));         // Hell0, w0rld!
print(s.replaceFirst('o', '0'));       // Hell0, world!

Extracting substrings. The substring method returns a portion of the string. The substring(start, end) form returns the characters from start to end (exclusive).

var s = 'Hello, world!';
print(s.substring(0, 5));    // Hello
print(s.substring(7));       // world!

Padding. The padLeft and padRight methods add characters to the beginning or end of a string until it reaches a specified length.

var s = '42';
print(s.padLeft(5, '0'));    // 00042
print(s.padRight(5, '*'));   // 42***

Character codes. The codeUnitAt and codeUnits methods expose the underlying UTF-16 code units. The runes property exposes the Unicode code points.

var s = 'A';
print(s.codeUnitAt(0));      // 65

var emoji = '๐Ÿ˜€';
print(emoji.length);         // 2 (two code units)
print(emoji.runes.length);   // 1 (one code point)

StringBuffer. For building strings from many pieces, StringBuffer is the efficient choice.

var buffer = StringBuffer();
for (var i = 0; i < 1000; i++) {
    buffer.write('$i ');
}
var result = buffer.toString();

The StringBuffer accumulates content in a growable buffer. The toString() call produces the final string. This is much faster than repeated + concatenation, which creates a new string on every iteration.


Complete Example Session

This session demonstrates interpolation, multiline literals, raw strings, and the common string methods.

void main() {
    // ============================================
    // PART 1: STRING INTERPOLATION
    // ============================================

    var name = 'Alice';
    var age = 30;

    print('Hello, $name!');                          // Hello, Alice!
    print('You are $age years old.');                // You are 30 years old.
    print('Uppercase: ${name.toUpperCase()}');       // Uppercase: ALICE
    print('Math: ${2 + 3}');                         // Math: 5
    print('Nested: ${'inner $name'}');               // Nested: inner Alice

    // Braces needed for disambiguation
    var file = 'report';
    print('${file}port');                            // reportport
    print('$file' 'port');                           // reportport

    // ============================================
    // PART 2: MULTILINE LITERALS
    // ============================================

    var multiline = '''
Line one
Line two
Line three''';

    print(multiline);

    // With trim
    var trimmed = '''
    Indented line one
    Indented line two
    '''.trim();

    print(trimmed);

    // ============================================
    // PART 3: RAW STRINGS
    // ============================================

    var regex = r'\d{3}-\d{4}';
    print(regex);                                    // \d{3}-\d{4}

    var windowsPath = r'C:\Users\Alice\Documents';
    print(windowsPath);                              // C:\Users\Alice\Documents

    var newlineLiteral = r'Line 1\nLine 2';
    print(newlineLiteral);                           // Line 1\nLine 2

    // ============================================
    // PART 4: LENGTH AND EMPTINESS
    // ============================================

    var s = 'hello';
    print(s.length);                                 // 5
    print(s.isEmpty);                                // false
    print(''.isEmpty);                               // true

    // ============================================
    // PART 5: SEARCHING
    // ============================================

    var text = 'Hello, world!';
    print(text.contains('world'));                   // true
    print(text.startsWith('Hello'));                 // true
    print(text.endsWith('!'));                       // true
    print(text.indexOf('o'));                        // 4
    print(text.lastIndexOf('o'));                    // 8
    print(text.indexOf('xyz'));                      // -1

    // ============================================
    // PART 6: CASE AND TRIMMING
    // ============================================

    print('Hello'.toUpperCase());                    // HELLO
    print('Hello'.toLowerCase());                    // hello
    print('   Hello   '.trim());                     // Hello
    print('   Hello   '.trimLeft());                 // Hello
    print('   Hello   '.trimRight());                // Hello

    // ============================================
    // PART 7: SPLITTING AND JOINING
    // ============================================

    var csv = 'one,two,three';
    var parts = csv.split(',');
    print(parts);                                    // [one, two, three]
    print(parts.join(' | '));                        // one | two | three

    // Split by regular expression
    var spaced = 'one  two   three';
    var words = spaced.split(RegExp(r'\s+'));
    print(words);                                    // [one, two, three]

    // ============================================
    // PART 8: REPLACING AND SUBSTRINGS
    // ============================================

    print('Hello, world!'.replaceAll('o', '0'));     // Hell0, w0rld!
    print('Hello, world!'.replaceFirst('o', '0'));   // Hell0, world!
    print('Hello, world!'.substring(0, 5));          // Hello
    print('Hello, world!'.substring(7));             // world!

    // ============================================
    // PART 9: PADDING AND CHARACTER CODES
    // ============================================

    print('42'.padLeft(5, '0'));                     // 00042
    print('42'.padRight(5, '*'));                    // 42***
    print('A'.codeUnitAt(0));                        // 65
    print('๐Ÿ˜€'.length);                               // 2
    print('๐Ÿ˜€'.runes.length);                         // 1

    // ============================================
    // PART 10: STRINGBUFFER
    // ============================================

    var buffer = StringBuffer();
    for (var i = 0; i < 5; i++) {
        buffer.write('Item $i ');
    }
    print(buffer.toString());                        // Item 0 Item 1 Item 2 Item 3 Item 4
}

The ten parts cover string interpolation, multiline literals, raw strings, length and emptiness, searching, case and trimming, splitting and joining, replacing and substrings, padding and character codes, and StringBuffer.


Quick Reference

The Interpolation Syntax

SyntaxPurposeExample
$identifierSimple variable'Hello, $name'
${expression}Complex expression'${user.name}'
\${...}Literal dollar sign'Cost: \$5'

The String Literal Forms

FormSyntaxEscape Sequences
Single quotes'...'Processed
Double quotes"..."Processed
Triple quotes'''...'''Processed
Raw singler'...'Literal
Raw tripler'''...'''Literal

The Common Methods

MethodPurpose
contains()Check for substring
startsWith()Check prefix
endsWith()Check suffix
indexOf()Find position
toUpperCase()Convert to uppercase
toLowerCase()Convert to lowercase
trim()Remove whitespace
split()Divide into parts
replaceAll()Replace all occurrences
substring()Extract portion
padLeft() / padRight()Add padding

The StringBuffer Methods

MethodPurpose
write()Append a value
writeln()Append a value with newline
writeAll()Append multiple values
toString()Produce the final string
clear()Reset the buffer

Best Practices

โœ… Do This:

// Use interpolation for building strings
var greeting = 'Hello, $name!';                                  // โœ…
// Use braces for disambiguation
var result = '${file}port';                                      // โœ…
// Use raw strings for regex and paths
var pattern = r'\d+';                                            // โœ…
// Use trim() with multiline literals
var text = '''
    Line one
    '''.trim();                                                  // โœ…
// Use StringBuffer for loop concatenation
var buffer = StringBuffer();
for (var item in items) buffer.write(item);                      // โœ…
// Use runes for character counting
var length = emoji.runes.length;                                 // โœ…

โŒ Don’t Do This:

// Don't concatenate in a loop with +
var result = '';
for (var i = 0; i < 1000; i++) {
    result += '$i';  // โš ๏ธ Creates 1000 intermediate strings
}
// Don't forget the r prefix for paths
var path = 'C:\Users\name';  // โŒ \U is an invalid escape
// Don't assume length equals character count
var emoji = '๐Ÿ˜€';
print(emoji.length);  // โš ๏ธ 2, not 1
// Don't assume string methods modify the original
var s = 'hello';
s.toUpperCase();  // โš ๏ธ Returns a new string, s is unchanged

Common Pitfalls

PitfallWhy It HappensFix
$fileport emptyShell reads fileport as variableUse ${file}port
Raw string errorBackslash before quoteUse adjacent literals
Unicode length wrongCounting UTF-16 unitsUse runes.length
Slow loop concatenationImmutable stringsUse StringBuffer
Escape sequence errorMissing r prefixAdd r prefix

Real-World Examples

1. Greeting

var greeting = 'Hello, $name!';

2. Disambiguation

var result = '${file}port';

3. Multiline

var text = '''
Line one
Line two
''';

4. Raw Regex

var pattern = r'\d{3}-\d{4}';

5. Raw Path

var path = r'C:\Users\Alice';

6. Search

if (text.contains('error')) { ... }

7. Split and Join

var parts = csv.split(',');
var joined = parts.join(' | ');

8. Replace

var cleaned = input.replaceAll(RegExp(r'\s+'), ' ');

9. Padding

var id = '42'.padLeft(5, '0');

10. StringBuffer

var buffer = StringBuffer();
for (var item in items) buffer.writeln(item);

Visual

String Interpolation

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  'Hello, $name!'                             โ”‚
โ”‚    โ”‚                                         โ”‚
โ”‚    โ””โ”€ $name โ†’ name.toString()                โ”‚
โ”‚                                              โ”‚
โ”‚  'Total: ${price * qty}'                     โ”‚
โ”‚    โ”‚                                         โ”‚
โ”‚    โ””โ”€ ${...} โ†’ evaluate, then toString()     โ”‚
โ”‚                                              โ”‚
โ”‚  '${file}port'                               โ”‚
โ”‚    โ”‚                                         โ”‚
โ”‚    โ””โ”€ braces separate variable from text     โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Raw vs Normal Strings

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  NORMAL: 'Line 1\nLine 2'                    โ”‚
โ”‚    โ†’ Line 1                                  โ”‚
โ”‚      Line 2                                  โ”‚
โ”‚                                              โ”‚
โ”‚  RAW: r'Line 1\nLine 2'                      โ”‚
โ”‚    โ†’ Line 1\nLine 2                          โ”‚
โ”‚                                              โ”‚
โ”‚  The r prefix disables all escape sequences. โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

StringBuffer vs Concatenation

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  CONCATENATION:                              โ”‚
โ”‚    var s = '';                               โ”‚
โ”‚    for (...) s += item;                      โ”‚
โ”‚    โ†’ creates N intermediate strings          โ”‚
โ”‚                                              โ”‚
โ”‚  STRINGBUFFER:                               โ”‚
โ”‚    var buf = StringBuffer();                 โ”‚
โ”‚    for (...) buf.write(item);                โ”‚
โ”‚    โ†’ accumulates in a growable buffer        โ”‚
โ”‚    โ†’ produces one final string               โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Unicode Length

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  '๐Ÿ˜€'                                        โ”‚
โ”‚    length โ†’ 2 (UTF-16 code units)            โ”‚
โ”‚    runes.length โ†’ 1 (Unicode code points)    โ”‚
โ”‚                                              โ”‚
โ”‚  For character counting, use runes.          โ”‚
โ”‚  For byte-level operations, use codeUnits.   โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
Interpolation$name or ${expression}
Braces neededWhen adjacent to text
Multiline'''...''' or """..."""
Raw stringr'...' or r'''...'''
LengthUTF-16 code units
RunesUnicode code points
ConcatenationAdjacent literals or +
Efficient buildingStringBuffer
Common methodscontains, split, trim, replaceAll, substring

Key takeaways:

  • String interpolation embeds expressions in strings. Use $identifier for simple identifiers and ${expression} for complex expressions. The braces are required when the variable is adjacent to text that could be mistaken for part of the name.
  • Multiline strings use triple quotes. The ''' and """ forms preserve newlines and indentation. Use trim() to remove the leading and trailing whitespace from source indentation.
  • Raw strings disable escape sequences. The r prefix makes backslashes literal. Raw strings are essential for regular expressions and Windows file paths.
  • Strings are immutable. Every method returns a new string. The original is never modified. This makes strings safe to share and safe to use as map keys.
  • The length property counts UTF-16 code units. For characters outside the Basic Multilingual Plane, such as emoji, length is larger than the visible character count. Use runes.length for Unicode code point counting.
  • StringBuffer is the efficient way to build strings in loops. Repeated + concatenation creates a new string on every iteration. StringBuffer accumulates content in a growable buffer and produces the final string once.
  • The split method accepts a Pattern. A string delimiter or a regular expression can be used. The join method on the resulting list combines the parts back into a single string.

Remember: Dart strings are immutable sequences of UTF-16 code units with a rich API. Interpolation embeds expressions. Multiline literals span lines. Raw strings treat every character literally. The StringBuffer builds strings efficiently. The length property counts code units, not characters. Understanding these rules prevents the most common mistakes and makes string handling natural.


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!