Watch the video (9 min)
On this page23 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. What Is a URI?
  5. 2. More Parts: Segments, Port and Values
  6. 3. Building a Uri
  7. 4. A Uri Never Changes
  8. 5. Encoding Text by Hand
  9. 6. Resolving Relative Addresses
  10. 7. Finding Patterns with RegExp
  11. 8. Replacing with a Pattern
  12. 9. Core Utilities Recap
  13. 10. Equality and Hash Codes Recap
  14. 11. A Real-World Example
  15. Code Examples
  16. Common Beginner Mistakes
  17. Best Practices
  18. Mini Challenge
  19. Practice Questions
  20. Practice Answers
  21. Quick Revision
  22. Key Takeaways
  23. What's Next?

Lecture 112 — URI & Core Utilities

Category18 — Files, JSON & Core Utilities
Lecture112 of 140
Estimated video length9 min
TargetDart 3.12.2
LevelIntermediate

Gluing web addresses together with + breaks the moment a space or an & sneaks in. A Uri takes addresses apart and builds them safely — and dart:core has 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'...', replaceAll and split (Lecture 008), clamp and round (Lecture 009)
  • == versus identical() (Lecture 018) and StringBuffer (Lecture 020)
  • overriding == and hashCode with Object.hash (Lecture 060)
  • import 'dart:math'; (Lecture 101) and FormatException (Lecture 108)

Connection With Previous Lectures

Sara's shop app needs a search link for whatever the customer types:

dart
void main() {
  var search = 'shoes & socks';
  var link = 'https://shop.example.com/search?q=' + search;
  print(Uri.parse(link).queryParameters);
}
output
{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, pathSegments and queryParameters
  • Build an address with Uri(...), Uri.https and Uri.http
  • Encode text for addresses with Uri.encodeComponent and Uri.encodeQueryComponent
  • Resolve a relative address against a base address
  • Search and replace text with RegExp
  • Recall StringBuffer, the dart:math helpers, identical and Object.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:

dart
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);
}
output
https
shop.example.com
/products/shoes
{color: red, size: 42}
reviews

The 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:

dart
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);
}
output
[products, shoes]
443
43

queryParameters 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:

dart
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']);
}
output
https://shop.example.com/search?q=shoes+%26+socks&page=2
shoes & socks

The & 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.

dart
void main() {
  print(Uri.https('shop.example.com', '/search', {'q': 'red shoes'}));
  print(Uri.http('localhost:8080', '/api/students'));
}
output
https://shop.example.com/search?q=red+shoes
http://localhost:8080/api/students

4. A Uri Never Changes

Like a String, a Uri is fixed once created. replace returns a new Uri with some parts swapped:

dart
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);
}
output
https://shop.example.com/search?q=shoes&page=1
https://shop.example.com/search?q=shoes&page=2

5. Encoding Text by Hand

Sometimes you only need one piece encoded. Two static methods do it:

dart
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));
}
output
Ali%20%26%20Sara%2F2026
Ali+%26+Sara%2F2026
Ali & Sara/2026

Both 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:

dart
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'));
}
output
https://docs.example.com/guide/setup.html
https://docs.example.com/api/index.html
https://docs.example.com/contact

A 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 Uri only describes an address. To actually download a page, Dart programs use the http package 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:

PatternMatches
\done digit
\sone space, tab or newline
\wone letter, digit or _
+the thing before it, one or more times
[A-Z]one capital letter
^ / $start / end of the text
dart
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());
}
output
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:

dart
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]'), ''));
}
output
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:

dart
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));
}
output
Marks: 78, 91, 85
91
1024
8
100

max, 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:

dart
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);
}
output
true
false
true
1

Equal 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:

dart
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);
}
output
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

dart
void main() {
  var uri = Uri.parse('http://localhost:8080/api/students/7');
  print(uri.host);
  print(uri.port);
  print(uri.pathSegments.last);
}
output
localhost
8080
7

Example 2 — Building with Uri

dart
void main() {
  var uri = Uri(scheme: 'https', host: 'school.example.com', path: '/results');
  print(uri);
}
output
https://school.example.com/results

Example 3 — Encoding a Round Trip

dart
void main() {
  var encoded = Uri.encodeComponent('100% done?');
  print(encoded);
  print(Uri.decodeComponent(encoded));
}
output
100%25%20done%3F
100% done?

Example 4 — Resolving

dart
void main() {
  var base = Uri.parse('https://school.example.com/class/10/marks.html');
  print(base.resolve('timetable.html'));
}
output
https://school.example.com/class/10/timetable.html

Example 5 — A Case-Insensitive Check

dart
void main() {
  var greeting = RegExp(r'^hello', caseSensitive: false);
  print(greeting.hasMatch('HELLO Sara'));
  print(greeting.hasMatch('Say hello'));
}
output
true
false

Example 6 — Joining with writeAll

dart
void main() {
  var buffer = StringBuffer()..writeAll(['Ali', 'Sara', 'Zayan'], ' | ');
  print(buffer);
  print(buffer.length);
}
output
Ali | Sara | Zayan
18

Example 7 — Student Marks from Text

dart
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)}');
}
output
[78, 91, 85]
Average: 84.7
dart
void main() {
  var link = Uri.https('shop.example.com', '/cart', {
    'item': ['apple', 'milk', 'bread'],
  });
  print(link);
  print(link.queryParametersAll['item']);
}
output
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.

dart
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}');
}
output
Pay Ali: 2500.5
Balance after: 7499.5

Example 10 — Hottest Temperature in a Report

dart
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)}');
}
output
[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

dart
void main() {
  // print(Uri.https('shop.example.com', '/search', {'page': 2})); // ✗
}
output
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

dart
void main() {
  // print(Uri.https('https://shop.example.com', '/search')); // ✗
}
output
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

dart
void main() {
  var uri = Uri.parse('https://shop.example.com/search?page=1');
  // uri.queryParameters['page'] = '2'; // ✗
}
output
Unhandled exception:
Unsupported operation: Cannot modify unmodifiable map

A Uri never changes. Use uri.replace(queryParameters: {...}).

5. Forgetting the r before a pattern

dart
void main() {
  print(RegExp('\d+').hasMatch('Room 42'));
  print(RegExp(r'\d+').hasMatch('Room 42'));
}
output
false
true

In 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

dart
void main() {
  // print(RegExp(r'\d+').firstMatch('no digits')!.group(0)); // ✗
}
output
Unhandled exception:
Null check operator used on a null value

firstMatch 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 use Uri.tryParse (which returns null) 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 == and hashCode together, with Object.hash over 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:

output
https://school.example.com/class/10/results?name=Ali+Khan&term=2
Ali Khan

Try it yourself before opening the solution.

Show solution
dart
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']);
}
output
https://school.example.com/class/10/results?name=Ali+Khan&term=2
Ali Khan

How it works

  1. resolve('results') adds the page to the base folder.
  2. replace attaches the encoded query; queryParameters decodes it again.

Practice Questions

Work through these in order — they run from beginner to application to challenge.

Beginner

  1. Parse https://weather.example.com/today?city=Lahore and print its host and the city value.
  2. Print Uri.encodeComponent('Sara & Ali').
  3. Use a RegExp to check whether 'Order 105' contains any digits.

Application

  1. Build https://shop.example.com/search?q=green+tea with Uri.https.
  2. Resolve ../index.html against https://site.example.com/blog/post.html.
  3. Replace every run of spaces in 'a b c' with a single dash.
  4. Print every number in 'Ali 78, Sara 91' as a list of strings.

Challenge

  1. Read page from https://shop.example.com/search?q=tea&page=3, add one, and print the new link built with replace.
  2. Check which of 'ali@mail.com' and 'ali.mail.com' match ^\w+@\w+\.\w+$.
  3. Write a class Product with name and price, overriding == and hashCode, and print the length of a set holding two equal products.

Practice Answers

Question 1 — Solution

dart
void main() {
  var uri = Uri.parse('https://weather.example.com/today?city=Lahore');
  print(uri.host);
  print(uri.queryParameters['city']);
}
output
weather.example.com
Lahore

Question 2 — Solution

dart
void main() {
  print(Uri.encodeComponent('Sara & Ali'));
}
output
Sara%20%26%20Ali

Question 3 — Solution

dart
void main() {
  print(RegExp(r'\d').hasMatch('Order 105'));
}
output
true

Question 4 — Solution

dart
void main() {
  print(Uri.https('shop.example.com', '/search', {'q': 'green tea'}));
}
output
https://shop.example.com/search?q=green+tea

Question 5 — Solution

dart
void main() {
  var base = Uri.parse('https://site.example.com/blog/post.html');
  print(base.resolve('../index.html'));
}
output
https://site.example.com/index.html

Question 6 — Solution

dart
void main() {
  print('a   b    c'.replaceAll(RegExp(r'\s+'), '-'));
}
output
a-b-c

Question 7 — Solution

dart
void main() {
  var matches = RegExp(r'\d+').allMatches('Ali 78, Sara 91');
  print(matches.map((m) => m.group(0)).toList());
}
output
[78, 91]

Question 8 — Solution

dart
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);
}
output
https://shop.example.com/search?q=tea&page=4

The spread ... copies the old parameters, then page overwrites the old value.

Question 9 — Solution

dart
void main() {
  var email = RegExp(r'^\w+@\w+\.\w+$');
  print(email.hasMatch('ali@mail.com'));
  print(email.hasMatch('ali.mail.com'));
}
output
true
false

Question 10 — Solution

dart
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);
}
output
1

Quick Revision

  1. Which Uri property gives the server name? Which gives the query as a map?
  2. Why should a query value be a String?
  3. What is the difference between encodeComponent and encodeQueryComponent?
  4. What does base.resolve('../x.html') do?
  5. Why must a pattern be written as r'...'?
  6. What does firstMatch return when nothing matches?
  7. Why must hashCode change together with ==?

Key Takeaways

  • Uri.parse splits an address into scheme, host, port, path, pathSegments, queryParameters and fragment.
  • Uri(...), Uri.https and Uri.http build addresses and encode them for you.
  • Query values are strings; a Uri never changes — use replace.
  • encodeComponent is for path pieces, encodeQueryComponent for query values.
  • resolve turns a relative link into a full address.
  • RegExp finds shapes of text: hasMatch, firstMatch, allMatches, and replaceAll.
  • StringBuffer, dart:math, identical and Object.hash complete 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.


Video lecture

Subscribe
Placeholder video — the real recording isn't linked yet.