On this page23 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. What Is a URI?
- 2. More Parts: Segments, Port and Values
- 3. Building a Uri
- 4. A Uri Never Changes
- 5. Encoding Text by Hand
- 6. Resolving Relative Addresses
- 7. Finding Patterns with RegExp
- 8. Replacing with a Pattern
- 9. Core Utilities Recap
- 10. Equality and Hash Codes Recap
- 11. A Real-World Example
- Code Examples
- Common Beginner Mistakes
- Best Practices
- Mini Challenge
- Practice Questions
- Practice Answers
- Quick Revision
- Key Takeaways
- What's Next?
Lecture 112 — URI & Core Utilities
| Category | 18 — Files, JSON & Core Utilities |
| Lecture | 112 of 140 |
| Estimated video length | 9 min |
| Target | Dart 3.12.2 |
| Level | Intermediate |
Gluing web addresses together with
+breaks the moment a space or an&sneaks in. AUritakes addresses apart and builds them safely — anddart:corehas a few more tools that every program reaches for.
Contents
Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways
Every error message in this lecture is real. Each one was produced by running the code shown on Dart 3.12.2, so you will recognize it when you meet it yourself.
Prerequisites
Students should already understand, from Lectures 001–111:
- raw strings
r'...',replaceAllandsplit(Lecture 008),clampandround(Lecture 009) ==versusidentical()(Lecture 018) andStringBuffer(Lecture 020)- overriding
==andhashCodewithObject.hash(Lecture 060) import 'dart:math';(Lecture 101) andFormatException(Lecture 108)
Connection With Previous Lectures
Sara's shop app needs a search link for whatever the customer types:
void main() {
var search = 'shoes & socks';
var link = 'https://shop.example.com/search?q=' + search;
print(Uri.parse(link).queryParameters);
}{q: shoes , socks: }The & was read as "a new parameter starts here", so the search became shoes plus an
empty parameter called socks. Building addresses by hand is fragile. This lecture shows the
safe way, then tours the small dart:core helpers we have not met yet.
Learning Objectives
By the end of this lecture you will be able to:
- Take a web address apart with
Uri.parse - Read
scheme,host,path,pathSegmentsandqueryParameters - Build an address with
Uri(...),Uri.httpsandUri.http - Encode text for addresses with
Uri.encodeComponentandUri.encodeQueryComponent - Resolve a relative address against a base address
- Search and replace text with
RegExp - Recall
StringBuffer, thedart:mathhelpers,identicalandObject.hash
1. What Is a URI?
A URI (Uniform Resource Identifier) is a structured address, like
https://shop.example.com/products/shoes?color=red. A web link (URL) is the most common kind.
Analogy — a URI is a postal address. It has fixed parts — country, city, street, house number — and the post office reads each part separately.
Uri.parse turns the text into a Uri object whose parts you can read:
void main() {
var uri = Uri.parse(
'https://shop.example.com/products/shoes?color=red&size=42#reviews',
);
print(uri.scheme);
print(uri.host);
print(uri.path);
print(uri.queryParameters);
print(uri.fragment);
}https
shop.example.com
/products/shoes
{color: red, size: 42}
reviewsThe scheme is the protocol, the host is the server, the path is the page, the
query comes after ?, and the fragment after #.
2. More Parts: Segments, Port and Values
pathSegments splits the path for you, and port knows the default for each scheme:
void main() {
var uri = Uri.parse('https://shop.example.com/products/shoes?size=42');
print(uri.pathSegments);
print(uri.port);
var size = int.parse(uri.queryParameters['size']!);
print(size + 1);
}[products, shoes]
443
43queryParameters is a Map<String, String>. Every value is text, so numbers need
int.parse. A missing key gives null, as with any map.
3. Building a Uri
To build an address, pass the parts by name. Dart adds ://, ?, & and the encoding:
void main() {
var uri = Uri(
scheme: 'https',
host: 'shop.example.com',
path: '/search',
queryParameters: {'q': 'shoes & socks', 'page': '2'},
);
print(uri);
print(uri.queryParameters['q']);
}https://shop.example.com/search?q=shoes+%26+socks&page=2
shoes & socksThe & inside the search became %26 and the spaces became +, so reading it back gives
the exact text. The problem from the Connection is solved.
Uri.https and Uri.http are shortcuts: host, path, then the query map.
void main() {
print(Uri.https('shop.example.com', '/search', {'q': 'red shoes'}));
print(Uri.http('localhost:8080', '/api/students'));
}https://shop.example.com/search?q=red+shoes
http://localhost:8080/api/students4. A Uri Never Changes
Like a String, a Uri is fixed once created. replace returns a new Uri with some
parts swapped:
void main() {
var page1 = Uri.https('shop.example.com', '/search', {
'q': 'shoes',
'page': '1',
});
var page2 = page1.replace(queryParameters: {'q': 'shoes', 'page': '2'});
print(page1);
print(page2);
}https://shop.example.com/search?q=shoes&page=1
https://shop.example.com/search?q=shoes&page=25. Encoding Text by Hand
Sometimes you only need one piece encoded. Two static methods do it:
void main() {
var text = 'Ali & Sara/2026';
var path = Uri.encodeComponent(text);
var query = Uri.encodeQueryComponent(text);
print(path);
print(query);
print(Uri.decodeComponent(path));
}Ali%20%26%20Sara%2F2026
Ali+%26+Sara%2F2026
Ali & Sara/2026Both replace unsafe characters with % codes. encodeComponent writes a space as %20 —
use it for a path piece. encodeQueryComponent writes a space as + — use it for a
query value. decodeComponent reverses the encoding.
6. Resolving Relative Addresses
A page often links to setup.html or ../api/ instead of a full address. resolve works
out the full address from a base:
void main() {
var base = Uri.parse('https://docs.example.com/guide/intro.html');
print(base.resolve('setup.html'));
print(base.resolve('../api/index.html'));
print(base.resolve('/contact'));
}https://docs.example.com/guide/setup.html
https://docs.example.com/api/index.html
https://docs.example.com/contactA plain name replaces the last segment, ../ goes up one folder, and / starts from the
host. This is exactly what a browser does when you click a link.
A
Urionly describes an address. To actually download a page, Dart programs use thehttppackage we added in Lecture 105; making real network requests is outside this course.
7. Finding Patterns with RegExp
contains('42') finds one exact text. A regular expression finds a shape of text —
"any digits", "a word, then @". Always write the pattern as a raw string:
| Pattern | Matches |
|---|---|
\d | one digit |
\s | one space, tab or newline |
\w | one letter, digit or _ |
+ | the thing before it, one or more times |
[A-Z] | one capital letter |
^ / $ | start / end of the text |
void main() {
var digits = RegExp(r'\d+');
print(digits.hasMatch('Room 42'));
print(digits.firstMatch('Room 12, floor 3')?.group(0));
var all = digits.allMatches('Room 12, floor 3');
print(all.map((m) => m.group(0)).toList());
}true
12
[12, 3]hasMatch answers yes or no. firstMatch returns the first match, or null — hence ?..
group(0) is the matched text. allMatches returns every match.
8. Replacing with a Pattern
replaceAll accepts a RegExp as well as a plain string:
void main() {
var messy = 'Sara Khan \t Lahore';
print(messy.replaceAll(RegExp(r'\s+'), ' '));
var phone = '0300-123 4567';
print(phone.replaceAll(RegExp(r'[^\d]'), ''));
}Sara Khan Lahore
03001234567\s+ squeezes every run of spaces and tabs into one space. [^\d] means "anything that is
not a digit", so removing those keeps only the numbers.
Add caseSensitive: false to ignore capitals: RegExp(r'^ali', caseSensitive: false)
matches Ali and ALI. Regular expressions are a large topic; these basics cover most
everyday checks.
9. Core Utilities Recap
A few tools from earlier lectures complete the everyday kit. StringBuffer builds text in
a loop without creating a new string each time, and writeAll joins a list:
import 'dart:math';
void main() {
var report = StringBuffer('Marks: ');
report.writeAll([78, 91, 85], ', ');
print(report);
print(max(78, 91));
print(pow(2, 10));
print((-7.6).abs().round());
print(120.clamp(0, 100));
}Marks: 78, 91, 85
91
1024
8
100max, min, pow and sqrt come from dart:math; abs, round and clamp are
methods on every number.
10. Equality and Hash Codes Recap
== asks "equal values?", identical asks "the same object?". A class that overrides ==
must override hashCode too, and Object.hash combines the fields:
class Point {
final int x;
final int y;
Point(this.x, this.y);
@override
bool operator ==(Object other) =>
other is Point && other.x == x && other.y == y;
@override
int get hashCode => Object.hash(x, y);
}
void main() {
var a = Point(1, 2);
var b = Point(1, 2);
print(a == b);
print(identical(a, b));
print(a.hashCode == b.hashCode);
print({a, b}.length);
}true
false
true
1Equal objects must have equal hash codes, or a Set or map key would treat them as
different. For a list of values, Object.hashAll(list) does the same job.
11. A Real-World Example
Zayan builds the search link for Sara's shop. He cleans the typed text, checks an optional product code, and prints a short report:
void main() {
var typed = ' blue running shoes ';
var code = 'SH-2041';
var search = typed.trim().replaceAll(RegExp(r'\s+'), ' ');
var validCode = RegExp(r'^[A-Z]{2}-\d{4}$').hasMatch(code);
var params = {'q': search, 'page': '1'};
if (validCode) params['code'] = code;
var link = Uri.https('shop.example.com', '/search', params);
var report = StringBuffer()
..writeln('Search: $search')
..writeln('Code valid: $validCode')
..write('Link: $link');
print(report);
}Search: blue running shoes
Code valid: true
Link: https://shop.example.com/search?q=blue+running+shoes&page=1&code=SH-2041{2} means "exactly two", so the pattern is two capitals, a dash, four digits. Uri.https
encodes the spaces, and the cascade fills the StringBuffer.
Code Examples
Example 1 — Reading Parts
void main() {
var uri = Uri.parse('http://localhost:8080/api/students/7');
print(uri.host);
print(uri.port);
print(uri.pathSegments.last);
}localhost
8080
7Example 2 — Building with Uri
void main() {
var uri = Uri(scheme: 'https', host: 'school.example.com', path: '/results');
print(uri);
}https://school.example.com/resultsExample 3 — Encoding a Round Trip
void main() {
var encoded = Uri.encodeComponent('100% done?');
print(encoded);
print(Uri.decodeComponent(encoded));
}100%25%20done%3F
100% done?Example 4 — Resolving
void main() {
var base = Uri.parse('https://school.example.com/class/10/marks.html');
print(base.resolve('timetable.html'));
}https://school.example.com/class/10/timetable.htmlExample 5 — A Case-Insensitive Check
void main() {
var greeting = RegExp(r'^hello', caseSensitive: false);
print(greeting.hasMatch('HELLO Sara'));
print(greeting.hasMatch('Say hello'));
}true
falseExample 6 — Joining with writeAll
void main() {
var buffer = StringBuffer()..writeAll(['Ali', 'Sara', 'Zayan'], ' | ');
print(buffer);
print(buffer.length);
}Ali | Sara | Zayan
18Example 7 — Student Marks from Text
void main() {
var line = 'Ali: 78, Sara: 91, Zayan: 85';
var marks = RegExp(
r'\d+',
).allMatches(line).map((m) => int.parse(m.group(0)!)).toList();
var average = marks.reduce((a, b) => a + b) / marks.length;
print(marks);
print('Average: ${average.toStringAsFixed(1)}');
}[78, 91, 85]
Average: 84.7Example 8 — Shopping Cart Link
void main() {
var link = Uri.https('shop.example.com', '/cart', {
'item': ['apple', 'milk', 'bread'],
});
print(link);
print(link.queryParametersAll['item']);
}https://shop.example.com/cart?item=apple&item=milk&item=bread
[apple, milk, bread]A list value repeats the key. queryParametersAll reads all the values back.
Example 9 — Bank Payment Link
void main() {
var link = Uri.parse('https://bank.example.com/pay?to=Ali&amount=2500.50');
var to = link.queryParameters['to'];
var amount = double.parse(link.queryParameters['amount']!);
var balance = 10000.0;
print('Pay $to: $amount');
print('Balance after: ${balance - amount}');
}Pay Ali: 2500.5
Balance after: 7499.5Example 10 — Hottest Temperature in a Report
import 'dart:math';
void main() {
var report = 'Mon 18.5C, Tue 21C, Wed -2.5C, Thu 24C';
var temps = RegExp(
r'-?\d+(\.\d+)?',
).allMatches(report).map((m) => double.parse(m.group(0)!)).toList();
print(temps);
print('Hottest: ${temps.reduce(max)}');
}[18.5, 21.0, -2.5, 24.0]
Hottest: 24.0-? is an optional minus sign; (\.\d+)? is an optional decimal part.
Common Beginner Mistakes
1. Building addresses with +
As the Connection showed, '...?q=' + 'shoes & socks' splits the search at the &. Use
Uri.https or Uri(...) with queryParameters.
2. Putting a number in queryParameters
void main() {
// print(Uri.https('shop.example.com', '/search', {'page': 2})); // ✗
}Unhandled exception:
type 'int' is not a subtype of type 'Iterable<dynamic>'It compiles but crashes at run time. Values must be String (or a list of strings): write
'2' or page.toString().
3. Including the scheme in Uri.https
void main() {
// print(Uri.https('https://shop.example.com', '/search')); // ✗
}Unhandled exception:
FormatException: Invalid radix-10 number (at character 1)
//shop.example.com
^Dart read https as the host and the rest as a port number. Pass only shop.example.com.
4. Trying to change queryParameters
void main() {
var uri = Uri.parse('https://shop.example.com/search?page=1');
// uri.queryParameters['page'] = '2'; // ✗
}Unhandled exception:
Unsupported operation: Cannot modify unmodifiable mapA Uri never changes. Use uri.replace(queryParameters: {...}).
5. Forgetting the r before a pattern
void main() {
print(RegExp('\d+').hasMatch('Room 42'));
print(RegExp(r'\d+').hasMatch('Room 42'));
}false
trueIn a normal string \d is just the letter d, so the first pattern looks for ds. No error
appears — only wrong results. Always write r'...'.
6. Using ! on firstMatch
void main() {
// print(RegExp(r'\d+').firstMatch('no digits')!.group(0)); // ✗
}Unhandled exception:
Null check operator used on a null valuefirstMatch returns null when nothing matches. Use ?. or check for null first.
Best Practices
- Build addresses with
Uri.https/Uri(...), never with+. - Read addresses with
Uri.parse, and useUri.tryParse(which returnsnull) for user input that may be invalid. - Pass query values as strings, and convert them back with
int.parse/double.parse. - Write every regular expression as a raw string
r'...'. - Anchor validation patterns with
^and$so the whole text must match. - Override
==andhashCodetogether, withObject.hashover the same fields.
Mini Challenge
Ali's school site has the base https://school.example.com/class/10/. Build the link to
results with the query name=Ali Khan and term=2, and print the student's name read
back from the link.
Your program should print exactly:
https://school.example.com/class/10/results?name=Ali+Khan&term=2
Ali KhanTry it yourself before opening the solution.
Show solution
void main() {
var base = Uri.parse('https://school.example.com/class/10/');
var link = base
.resolve('results')
.replace(queryParameters: {'name': 'Ali Khan', 'term': '2'});
print(link);
print(link.queryParameters['name']);
}https://school.example.com/class/10/results?name=Ali+Khan&term=2
Ali KhanHow it works
resolve('results')adds the page to the base folder.replaceattaches the encoded query;queryParametersdecodes it again.
Practice Questions
Work through these in order — they run from beginner to application to challenge.
Beginner
- Parse
https://weather.example.com/today?city=Lahoreand print its host and thecityvalue. - Print
Uri.encodeComponent('Sara & Ali'). - Use a
RegExpto check whether'Order 105'contains any digits.
Application
- Build
https://shop.example.com/search?q=green+teawithUri.https. - Resolve
../index.htmlagainsthttps://site.example.com/blog/post.html. - Replace every run of spaces in
'a b c'with a single dash. - Print every number in
'Ali 78, Sara 91'as a list of strings.
Challenge
- Read
pagefromhttps://shop.example.com/search?q=tea&page=3, add one, and print the new link built withreplace. - Check which of
'ali@mail.com'and'ali.mail.com'match^\w+@\w+\.\w+$. - Write a class
Productwithnameandprice, overriding==andhashCode, and print the length of a set holding two equal products.
Practice Answers
Question 1 — Solution
void main() {
var uri = Uri.parse('https://weather.example.com/today?city=Lahore');
print(uri.host);
print(uri.queryParameters['city']);
}weather.example.com
LahoreQuestion 2 — Solution
void main() {
print(Uri.encodeComponent('Sara & Ali'));
}Sara%20%26%20AliQuestion 3 — Solution
void main() {
print(RegExp(r'\d').hasMatch('Order 105'));
}trueQuestion 4 — Solution
void main() {
print(Uri.https('shop.example.com', '/search', {'q': 'green tea'}));
}https://shop.example.com/search?q=green+teaQuestion 5 — Solution
void main() {
var base = Uri.parse('https://site.example.com/blog/post.html');
print(base.resolve('../index.html'));
}https://site.example.com/index.htmlQuestion 6 — Solution
void main() {
print('a b c'.replaceAll(RegExp(r'\s+'), '-'));
}a-b-cQuestion 7 — Solution
void main() {
var matches = RegExp(r'\d+').allMatches('Ali 78, Sara 91');
print(matches.map((m) => m.group(0)).toList());
}[78, 91]Question 8 — Solution
void main() {
var uri = Uri.parse('https://shop.example.com/search?q=tea&page=3');
var page = int.parse(uri.queryParameters['page']!) + 1;
var next = uri.replace(
queryParameters: {...uri.queryParameters, 'page': '$page'},
);
print(next);
}https://shop.example.com/search?q=tea&page=4The spread ... copies the old parameters, then page overwrites the old value.
Question 9 — Solution
void main() {
var email = RegExp(r'^\w+@\w+\.\w+$');
print(email.hasMatch('ali@mail.com'));
print(email.hasMatch('ali.mail.com'));
}true
falseQuestion 10 — Solution
class Product {
final String name;
final double price;
Product(this.name, this.price);
@override
bool operator ==(Object other) =>
other is Product && other.name == name && other.price == price;
@override
int get hashCode => Object.hash(name, price);
}
void main() {
var cart = {Product('Tea', 450), Product('Tea', 450)};
print(cart.length);
}1Quick Revision
- Which
Uriproperty gives the server name? Which gives the query as a map? - Why should a query value be a
String? - What is the difference between
encodeComponentandencodeQueryComponent? - What does
base.resolve('../x.html')do? - Why must a pattern be written as
r'...'? - What does
firstMatchreturn when nothing matches? - Why must
hashCodechange together with==?
Key Takeaways
Uri.parsesplits an address intoscheme,host,port,path,pathSegments,queryParametersandfragment.Uri(...),Uri.httpsandUri.httpbuild addresses and encode them for you.- Query values are strings; a
Urinever changes — usereplace. encodeComponentis for path pieces,encodeQueryComponentfor query values.resolveturns a relative link into a full address.RegExpfinds shapes of text:hasMatch,firstMatch,allMatches, andreplaceAll.StringBuffer,dart:math,identicalandObject.hashcomplete the everyday kit.
What's Next?
Lecture 113 — Dart CLI. This closes the Files, JSON & Core Utilities category. So far
dart run has been our only command. Next we open the developer toolbox and meet the other
dart commands for creating, checking and compiling projects.