1.String Manipulation¶
If you came to this chapter hoping to learn how to knit or crochet, you’ll have to find another book. In this chapter, you’ll learn how to manipulate text by adding and removing characters, splitting and re-joining strings, and performing search-and-replace operations. Another essential skill this chapter will teach you is how to validate user input. Regular expressions are a powerful tool for that, and in addition to string validation, you can also use them to extract text. Hold on to your hat because you’re about to say goodbye to the land of beginners.
Basic String Manipulation¶
This section will start with a few easy ways to modify strings. These string manipulation methods are easy because they’re built right into the String
type. Anytime you have a string, they’re just one .
away.
Changing the Case¶
Strings are case sensitive, which means Hello
is different than hello
, which is different than HELLO
. This can be a problem if you’re using email addresses as unique identifiers in your database. Email addresses are inherently not case sensitive. You don’t want to create different user accounts for spongebob@example.com
and SpongeBob@example.com
, do you? And then there are always those users who are still living off memes from the last decade and give you sPoNgEbOb@eXaMpLe.cOm
. No worries, though. Dart is here to save the day.
Write the following code in main
:
const userInput = 'sPoNgEbOb@eXaMpLe.cOm';
final lowercase = userInput.toLowerCase();
print(lowercase);
The method toLowerCase
creates a new string where all the capital letters are lowercase.
Run that, and you’ll get a string your database will thank you for:
spongebob@example.com
If you wish to go the other way, you can call toUpperCase
.
Adding and Removing at the Ends¶
The beginning or end of a string sometimes needs a little work to create the form you want.
Trimming¶
One common thing you’ll want to remove is extra whitespace at the beginning or end of a string. Whitespace can be problematic because two strings might appear to be the same but are actually different. Removing this whitespace is called trimming.
Replace the contents of main
with the following:
const userInput = ' 221B Baker St. ';
final trimmed = userInput.trim();
print(trimmed); // '221B Baker St.'
trimmed
no longer contains the extra spaces at the beginning or end of the string. This works for not only the space character but also the newline character, tab character or any other Unicode-defined White_Space
character.
Use trimLeft
or trimRight
if you only need to trim whitespace from one end.
Padding¶
In contrast to trimming, sometimes you need to add extra space or other characters to the beginning or end of a string. For example, what if you’re making a digital clock? The naive approach would be to form your string like so:
final time = Duration(hours: 1, minutes: 32, seconds: 57);
final hours = time.inHours;
final minutes = time.inMinutes % 60;
final seconds = time.inSeconds % 60;
final timeString = '$hours:$minutes:$seconds';
print(timeString); // 1:32:57
You need to take the remainder after dividing by 60 to get minutes
and seconds
because there might be more than 59 minutes and seconds in some duration, which is true in this case where the total duration is over an hour.
Running the code above gives a result of 1:32:57
. This is reasonable for a digital clock. However, changing the duration slightly will show the problem. Replace the first line above with the following:
final time = Duration(hours: 1, minutes: 2, seconds: 3);
Rerun your code, and you’ll see the new result of timeString
:
1:2:3
That doesn’t look much like a time string anymore. What you want is 1:02:03
.
Dart is here to the rescue again, this time with the padLeft
method. You can use padLeft
to add any character, but in this case, you want to add zeros to the left of numbers less than 10.
Replace the code above with the new version:
final time = Duration(hours: 1, minutes: 2, seconds: 3);
final hours = time.inHours;
final minutes = '${time.inMinutes % 60}'.padLeft(2, '0');
final seconds = '${time.inSeconds % 60}'.padLeft(2, '0');
final timeString = '$hours:$minutes:$seconds';
print(timeString);
The 2
in padLeft(2, '0')
means you want the minimum length to be two characters long. The '0'
is the padding character you want to use. If you hadn’t specified that, the padding would have defaulted to the space character.
Run the code again. This time, you’ll see the following result:
1:02:03
That’s much better.
As you might have guessed, you can also use a padRight
method to add characters to the end of a string.
Splitting and Joining¶
Developers often use strings to combine many small pieces of data. One such example is the lines of a comma-separated values (CSV) file. In such a file, each line contains data items called fields, which commas separate. Here’s a sample file:
Martin,Emma,12,Paris,France
Smith,John,37,Chicago,USA
Weber,Hans,52,Berlin,Germany
Bio,Marie,33,Cotonou,Benin
Wang,Ming,40,Shanghai,China
Hernández,Isabella,23,Mexico City,Mexico
Nergui,Bavuudorj,21,Ulaanbaatar,Mongolia
The fields in this CSV file are ordered by surname, given name, age, city and country.
Take just one line of that file. Here’s how you would split that string at the commas to access the fields:
const csvFileLine = 'Martin,Emma,12,Paris,France';
final fields = csvFileLine.split(',');
print(fields);
The split
method can split the string by any character, but here you specify that you want it to split at ','
.
Run that code, and you’ll see that fields
contains a list of strings like so:
[Martin, Emma, 12, Paris, France]
Note that those are all separate strings now, which you can easily access. You learned how to access the elements of a list in Dart Apprentice: Fundamentals, Chapter 12, “Lists”.
You can also go the other direction. Given some list of strings, you can join all the elements together using the join
method on List
. This time use a dash instead of a comma for a little extra variety:
final joined = fields.join('-');
Print joined
, and you’ll see the following result:
Martin-Emma-12-Paris-France
Replacing¶
Find-and-replace is a common task you perform on any text document. You can also do the same thing programmatically. For example, say you want to replace all the spaces with underscores in some text. You can do this easily using the replaceAll
method.
Write the following in main
:
const phrase = 'live and learn';
final withUnderscores = phrase.replaceAll(' ', '_');
print(withUnderscores);
The first argument you give to replaceAll
is the string you want to match — in this case, the space character. The second argument is the replacement string, in this case, an underscore.
Run the code above, and you’ll see the following result:
live_and_learn
If you only need to replace the first occurrence of some pattern, use replaceFirst
instead of replaceAll
.
Exercises¶
- Take a multiline string, such as the text below, and split it into a list of single lines. Hint: Split at the newline character.
France
USA
Germany
Benin
China
Mexico
Mongolia
- Find an emoji online to replace
:]
with in the following text:
How's the Dart book going? :]
Building Strings¶
You learned about string concatenation in Dart Apprentice: Fundamentals, Chapter 4, “Strings”, with the following example:
var message = 'Hello' + ' my name is ';
const name = 'Ray';
message += name;
// 'Hello my name is Ray'
But using the +
operator isn’t efficient when building up long strings one piece at a time. The reason is that Dart strings are immutable — that is, they can’t be changed — so every time you add two strings together, Dart has to create a new object for the concatenated string.
Improving Efficiency With String Buffers¶
A more efficient method of building strings is to use the StringBuffer
class. The word “buffer” refers to a storage area you can modify in the computer’s memory. StringBuffer
allows you to add strings to the internal buffer without needing to create a new object every time. When you finish building the string, you just convert the StringBuffer
contents to String
.
Here’s the previous example rewritten using a string buffer:
final message = StringBuffer();
message.write('Hello');
message.write(' my name is ');
message.write('Ray');
message.toString();
// 'Hello my name is Ray'
Calling toString
converts the string buffer to the String
type. This is like the type conversion you’ve seen when calling toInt
to convert a double
to the int
type.
Building Strings in a Loop¶
Typically, you’ll use a string buffer inside a loop, where every iteration adds a little more to the string.
Write the following for
loop in main
:
for (int i = 2; i <= 1024; i *= 2) {
print(i);
}
This prints powers of 2 up through 1024.
Run that, and you’ll get the following result:
2
4
8
16
32
64
128
256
512
1024
Each power of two is printed on a new line. What if you wanted to print the numbers on a single line, though, like so:
2 4 8 16 32 64 128 256 512 1024
The print
statement doesn’t allow you to do that directly. However, if you build the string first, you can print it when you’re finished.
Add this modified for
loop at the end of main
:
final buffer = StringBuffer();
for (int i = 2; i <= 1024; i *= 2) {
buffer.write(i);
buffer.write(' ');
}
print(buffer);
In every loop, you write the number to the buffer and add a space. There’s no need to call buffer.toString()
in this case because the print
statement handles that internally.
Run the code above, and you should see the expected result:
2 4 8 16 32 64 128 256 512 1024
Here are a few more situations where a string buffer will come in handy:
- Listening to a stream of data coming from the network.
- Processing a text file one line at a time.
- Building a string from multiple database queries.
Exercise¶
Use a string buffer to build the following string:
*********
* ********
** *******
*** ******
**** *****
***** ****
****** ***
******* **
******** *
*********
Hint: Use a loop inside a loop.
String Validation¶
- Hello, I’m a user of your app, and my telephone number is
555732872937482748927348728934723937489274
. - Hello, I’m a user of your app, and my credit card number is
Pizza
. - Hello, I’m a user of your app, and my address is
'; DROP TABLE users; --
.
You should never trust user input. It’s not that everyone is a hacker trying to break into your system — though you need to be on your guard against that, too — it’s just that a lot of the time, innocent users make simple typing mistakes. It’s your job to make sure you only allow data that’s in the proper format.
Verifying that user text input is in the proper form is called string validation. Here are a few common examples of string data you should validate:
- Telephone numbers
- Credit card numbers
- Email addresses
- Passwords
Even though some of these are “numbers”, you’ll still process them as strings.
Checking the Contents of a String¶
The String
class contains several methods that will help you validate the contents of a string. To demonstrate that functionality, write the following line in main
:
const text = 'I love Dart';
You can check whether that string begins with the letter I
using startsWith
. Add the following line at the end of main
:
print(text.startsWith('I')); // true
startsWith
returns a Boolean value, which is true
in this case. Verify that by running the code.
Similarly, you can use endsWith
to check the end of a string:
print(text.endsWith('Dart')); // true
This is also true
.
And if you want to check the middle of a string, use contains
:
print(text.contains('love')); // true
print(text.contains('Flutter')); // false
These examples are all very nice, but how would you verify that a phone number contains only numbers or a password contains upper and lowercase letters, numbers and special characters?
One possible solution would be to loop through every character and check whether its code unit value falls within specific Unicode ranges.
For example, an uppercase letter must fall within the Unicode range of 65-90, a lowercase letter within 97-122, a number within 48-57 and special characters within other ranges, depending on the specific characters you want to allow.
Checking every character like this would be tedious, though. There’s an easier way, which you’ll learn about in the next section.
Regular Expressions¶
Regular expressions, sometimes called regex, express complex matching patterns in an abbreviated form. Most programming languages support them, and Dart is no exception. Although there are some syntax variations between languages, the differences are minor. Dart shares the same syntax as regular expressions in JavaScript.
Matching Literal Characters¶
Use the RegExp
class to create a regex matching pattern in Dart.
Write the following in main
:
final regex = RegExp('cat');
There are a few ways you can use this pattern. One is to call the hasMatch
method like so:
print(regex.hasMatch('concatenation')); // true
print(regex.hasMatch('dog')); // false
print(regex.hasMatch('cats')); // true
hasMatch
returns true
if the regex pattern matches the input string. In this case, both concatenation
and cats
contain the substring cat
, so these return true
, whereas dog
returns false
because it doesn’t match the string literal cat
.
An alternative method to accomplish the same task would be to use the contains
method on String
like you did earlier:
print('concatenation'.contains(regex)); // true
print('dog'.contains(regex)); // false
print('cats'.contains(regex)); // true
The results are the same.
Matching string literals has limited use. The power of regular expressions is in the special characters.
Matching Any Single Character¶
Regular expressions use special characters that act as wildcards. You can use them to match more than just literal characters.
The .
dot character, for example, will match any single character.
Try the following example:
final matchSingle = RegExp('c.t');
print(matchSingle.hasMatch('cat')); // true
print(matchSingle.hasMatch('cot')); // true
print(matchSingle.hasMatch('cut')); // true
print(matchSingle.hasMatch('ct')); // false
Because the .
matches any single character, it will match the a
of cat
, the o
of cot
and the u
of cut
. This gives you much more flexibility in what you match.
The regex pattern c.t
didn’t match the string ct
because .
always matches one character. If you want to also match ct
, use the pattern c.?t
, where the ?
question mark is a special regex character that optionally matches the character before it. Because the previous character is .
, the pattern .?
matches one or zero instances of any character.
Look at the modified example that uses c.?t
:
final optionalSingle = RegExp('c.?t');
print(optionalSingle.hasMatch('cat')); // true
print(optionalSingle.hasMatch('cot')); // true
print(optionalSingle.hasMatch('cut')); // true
print(optionalSingle.hasMatch('ct')); // true
This time all the inputs match.
Matching Multiple Characters¶
Two special characters enable you to match more than one character:
+
: The plus sign means the character it follows can occur one or more times.\*
: The asterisk means the character it follows can occur zero or more times.
Write the following examples to see how they work:
final oneOrMore = RegExp('wo+w');
print(oneOrMore.hasMatch('ww')); // false
print(oneOrMore.hasMatch('wow')); // true
print(oneOrMore.hasMatch('wooow')); // true
print(oneOrMore.hasMatch('wooooooow')); // true
final zeroOrMore = RegExp('wo*w');
print(zeroOrMore.hasMatch('ww')); // true
print(zeroOrMore.hasMatch('wow')); // true
print(zeroOrMore.hasMatch('wooow')); // true
print(zeroOrMore.hasMatch('wooooooow')); // true
o+
matched o
, ooo
and ooooooo
but not the empty space between the w’s of ww
. On the other hand, o*
matched everything, even the empty space.
If you want to allow multiple instances of any character, combine .
with +
or *
. Write the following example:
final anyOneOrMore = RegExp('w.+w');
print(anyOneOrMore.hasMatch('ww')); // false
print(anyOneOrMore.hasMatch('wow')); // true
print(anyOneOrMore.hasMatch('w123w')); // true
print(anyOneOrMore.hasMatch('wABCDEFGw')); // true
Here you use the combination .+
to match o
, 123
and ABCDEFG
.
Matching Sets of Characters¶
The .
regex will match any character, but it’s often useful to match a limited set or range of characters. You can accomplish that using []
square brackets. Only the characters you put inside the square brackets will be used to find a match.
final set = RegExp('b[oa]t');
print(set.hasMatch('bat')); // true
print(set.hasMatch('bot')); // true
print(set.hasMatch('but')); // false
print(set.hasMatch('boat')); // false
print(set.hasMatch('bt')); // false
The set [ao]
matches one a
or one o
but not both.
You can also specify ranges inside the brackets if you use the -
dash character:
final letters = RegExp('[a-zA-Z]');
print(letters.hasMatch('a')); // true
print(letters.hasMatch('H')); // true
print(letters.hasMatch('3z')); // true
print(letters.hasMatch('2')); // false
The regex '[a-zA-Z]'
contains two ranges: all of the lowercase letters from a
to z
and all of the uppercase letters from A
to Z
. There will be a match as long as the input string has at least one lower or uppercase letter.
If you want to specify which characters not to match, add ^
just after the left bracket:
final excluded = RegExp('b[^ao]t');
print(excluded.hasMatch('bat')); // false
print(excluded.hasMatch('bot')); // false
print(excluded.hasMatch('but')); // true
print(excluded.hasMatch('boat')); // false
print(excluded.hasMatch('bt')); // false
[^ao]
matches one of any character except a
or o
.
Escaping Special Characters¶
What if you want to match a special character itself? You can escape it by prefixing the special character with a \
backslash. However, because the backslash is also a special character in Dart strings, it’s usually better to use raw Dart strings whenever you create regular expressions. Do you still remember how to create raw strings in Dart? Prefix them with r
, which stands for “raw”.
final escaped = RegExp(r'c\.t');
print(escaped.hasMatch('c.t')); // true
print(escaped.hasMatch('cat')); // false
If you hadn’t prefixed the regex pattern with r
, you would have needed to write 'c\\.t'
with two backslashes, one to escape the \
special character in Dart and one to escape the .
special character in regular expressions.
In the future, this book will always use raw Dart strings for regular expressions. The only reason you wouldn’t is if you needed to insert a Dart variable using interpolation. See Dart Apprentice: Fundamentals, Chapter 4, “Strings”, for a review on string interpolation.
Matching the Beginning and End¶
If you want to validate that a phone number contains only numbers, you might expect to use the following regular expression:
final numbers = RegExp('r[0-9]');
This does match the range of numbers from 0 to 9. However, you’ll discover a problem if you try to match the following cases:
print(numbers.hasMatch('5552021')); // true
print(numbers.hasMatch('abcefg2')); // true
That second one shouldn’t be a valid phone number, but it passes your validation check because it does contain the number 2.
What you want is for every character to be a number.
You can use the following regex to accomplish that:
final onlyNumbers = RegExp(r'^[0-9]+$');
print(onlyNumbers.hasMatch('5552021')); // true
print(onlyNumbers.hasMatch('abcefg2')); // false
The regex ^[0-9]+$
is a bit cryptic, so here’s the breakdown:
^
: Matches the beginning of the string.[0-9]
: Matches one number in the range 0-9.+
: Matches one or more instances of the previous character, in this case, one or more numbers in the range 0-9.$
: Matches the end of the string.
In summary, the regex ^[0-9]+$
only will match strings that contain numbers from start to end.
Note
The ^
character has two meanings in regex. When used inside []
square brackets, it means “not”. When used elsewhere, it matches the beginning of the line.
Example: Validating a Password¶
Here’s how you might validate a password where you require the password to contain at least one of each of the following:
- Lowercase letter.
- Uppercase letter.
- Number.
Write the following code in main
to demonstrate how this would work:
const password = 'Password1234';
final lowercase = RegExp(r'[a-z]');
final uppercase = RegExp(r'[A-Z]');
final number = RegExp(r'[0-9]');
if (!password.contains(lowercase)) {
print('Your password must have a lowercase letter!');
} else if (!password.contains(uppercase)) {
print('Your password must have an uppercase letter!');
} else if (!password.contains(number)) {
print('Your password must have a number!');
} else {
print('Your password is OK.');
}
This first checks for lowercase, then uppercase and finally numbers.
Run that to see the following result:
Your password is OK.
You probably noticed that a short password like Pw1
would still work, so you’ll also want to enforce a minimum length. One way to do that would be like so:
if (password.length < 12) {
print('Your password must be at least 12 characters long!');
}
You could also accomplish the same task by using a regular expression:
final goodLength = RegExp(r'.{12,}');
if (!password.contains(goodLength)) {
print('Your password must be at least 12 characters long!');
}
The {}
curly braces indicate a length range in regex. Using {12}
means a length of exactly 12, {12,15}
means a length of 12 to 15 characters, and {12,}
means a length of at least 12 with no upper limit. Because {12,}
follows the .
character, you’re allowing 12 or more of any character.
Note
Although regular expressions are powerful, they’re also notoriously hard to read. When you have a choice, go for the more readable option. In this case, using password.length
is perhaps the better choice. But that’s subjective, and the goodLength
name is also fairly readable, so you’ll have to make that call.
Regex Summary¶
The table below summarizes the regular expression special characters you’ve already learned, plus a few more you haven’t:
.
: Matches one of any character.?
: Zero or one match of the previous character.+
: One or more matches of the previous character.\*
: Zero or more matches of the previous character.{3}
: 3 matches of the previous character.{3,5}
: 3-5 matches of the previous character.{3,}
: 3 or more matches of the previous character.[]
: Matches one of any character inside the brackets.[^]
: Matches one of any character not inside the brackets.\
: Escapes the special character that follows.^
: Matches the beginning of a string or line.$
: Matches the end of a string or line.\d
: Matches one digit.\D
: Matches one non-digit.\s
: Matches one whitespace character.\S
: Matches one non-whitespace character.\w
: Matches one alphanumeric character. Same as[a-zA-Z0-9_]
.\W
: Matches one non-alphanumeric character.\uXXXX
: Matches a Unicode character where XXXX is the Unicode value.
This list isn’t exhaustive, but it should get you pretty far.
Exercise¶
Validate that a credit card number contains only numbers and is exactly 16 digits long.
Extracting text¶
Another common task when manipulating strings is extracting chunks of text from a longer string. You’ll learn two ways to accomplish this, one with substring
and another with regex groups.
Extracting Text With Substring¶
Start with the following simple HTML text document:
<!DOCTYPE html>
<html>
<body>
<h1>Dart Tutorial</h1>
<p>Dart is my favorite language.</p>
</body>
</html>
Finding a Single Match¶
Say you want to extract the text Dart Tutorial
, which is between the <h1>
and </h1>
tags.
Put the HTML file inside a multiline string like so:
const htmlText = '''
<!DOCTYPE html>
<html>
<body>
<h1>Dart Tutorial</h1>
<p>Dart is my favorite language.</p>
</body>
</html>
''';
Now, extract the desired text by writing the following:
final heading = htmlText.substring(34, 47);
print(heading); // Dart Tutorial
The D
of Dart Tutorial
is the 34th character in the string, and the final l
of Dart Tutorial
is the 46th character. The substring
method extracts a string between two indexes in a longer string. The start index is inclusive, and the end index is exclusive. Exclusive means the range doesn’t include that index. For example, if you write 47
as the end index, the last character in the range will be at index 46
. This might seem strange, but it works out well in a zero-based indexing system where the length of the string is also the end index of the final character.
You’re now probably asking, “How in the world do I know what the index numbers are?” Good question. The indexOf
method will help you with that.
Add the following code below what you wrote previously:
final start = htmlText.indexOf('<h1>') + '<h1>'.length; // 34
final end = htmlText.indexOf('</h1>'); // 47
heading = htmlText.substring(start, end);
print(heading);
Calling indexOf('<h1>')
finds where <h1>
begins in the text, which turns out to be at index 30
. To find the beginning of Dart Tutorial
, you need to add the length of the <h1>
tag itself, which is 4
. Adding 30
and 4
gives the start index of 34
. To find the end index, simply search for the closing tag </h1>
. Because the end index is exclusive, index 47
is exactly what you want.
Run the code again, and you’ll see the same result.
Finding Multiple Matches¶
What if there are multiple headers? In that case, you can provide a minimum start index to indexOf
as you loop through every match.
Replace main
with the following example:
const text = '''
<h1>Dart Tutorial</h1>
<h1>Flutter Tutorial</h1>
<h1>Other Tutorials</h1>
''';
var position = 0;
while (true) {
var start = text.indexOf('<h1>', position) + '<h1>'.length;
var end = text.indexOf('</h1>', position);
if (start == -1 || end == -1) {
break;
}
final heading = text.substring(start, end);
print(heading);
position = end + '</h1>'.length;
}
Here, you use position
to track where you are in the string. After extracting one match, you move position
to after the end
index to find the next match on the next loop. indexOf
will only find the first match after the specified position. If no match is found, then indexOf
will return -1
and you can stop searching.
Run the code, and you’ll see the extracted text:
Dart Tutorial
Flutter Tutorial
Other Tutorials
Extracting Text With Regex Groups¶
The other way to accomplish the same objective is to use regular expression groups. These are the same regular expressions you used when validating strings. The only thing you need to add is a pair of parentheses around the part you want to extract.
Using the same text
as in the last example, add the following code to the end of main
:
// 1
final headings = RegExp(r'<h1>(.+)</h1>');
// 2
final matches = headings.allMatches(text);
for (final match in matches) {
// 3
print(match.group(1));
}
Here are explanations of the numbered comments:
<h1>
and</h1>
match literal characters in the text, and.+
matches everything between them. Surrounding.+
with parentheses, as in(.+)
, marks this text as a regex group.- The original text has three headings that match the regex pattern, so
matches
will be a collection of three. group(1)
holds the text from the regex group you made earlier using parentheses. This example only used one set of parentheses. If you had used a second set of parentheses, you could access that text usinggroup(2)
.
Run the code, and you’ll see the text of the three matches printed to the console:
Dart Tutorial
Flutter Tutorial
Other Tutorials
Challenges¶
You’ve come a long way. Before going on, try out a few challenges to test your string manipulation ability. If you need the answers, you can find them in the supplemental materials accompanying the book.
Challenge 1: Email Validation¶
Write a regular expression to validate an email address.
Challenge 2: Karaoke Words¶
An LRC file contains the timestamps for the lyrics of a song. How would you extract the time and lyrics for the following line of text:
[00:12.34]Row, row, row your boat
Extract the relevant parts of the string and print them in the following format:
minutes: 00
seconds: 12
hundredths: 34
lyrics: Row, row, row your boat
Solve the problem twice, once with substring
and once with regex groups.
Key Points¶
- The
String
class contains many built-in methods to modify strings, includingtrim
,padLeft
,padRight
,split
,replaceAll
andsubstring
. - When building a string piece by piece, using
StringBuffer
is the most efficient. - Always validate user input.
- Regular expressions are a powerful way to match strings to a specified pattern.
- You can extract strings from text with
String.substring
or regex groups.
Where to Go From Here?¶
Regex in Your Editor¶
Regular expressions are not only useful for Dart code. You can use them in many editors as well. For example, in VS Code, press Command-F on a Mac or Control-F on a PC to show the Find bar. Select the Use Regular Expression button, and then you’ll be able to search powerfully through all of your code:
The example in the image above finds every line that begins with a capital letter.
Combine that with replacement, and you can even use regex groups. Use $1
in the Replacement field to capture the first group from the Find field.
The example in the image above would find something like this:
print(text.startsWith('I'))
And replace it with the following:
text.startsWith('I')
This effectively removes the whole print
statement in a single step!
String Validation Packages¶
Although any serious developer should know how to use regular expressions, you also don’t need to reinvent the wheel when it comes to string validation. Search pub.dev for “string validation” to find packages that probably already do what you need. You can always go to their source code and copy the regex pattern if you don’t want to add another dependency just for a single validation.