In Dart, how do you build a request Uri with query parameters safely, and why does Uri.https fail when a query value is an int?
answer
- Uri.https(authority, path, params)
- values: String, null or Iterable<String>
- keys and values encoded, space as +
- parse throws, tryParse returns null
- queryParametersAll for repeated keys
basics
~20 sBuild it with Uri.https(host, path, queryParameters) or the Uri() constructor, which percent-encode every key and value. Each value must be a String, null or an Iterable<String>, so an int fails at runtime; convert it with toString() first.
solid answer
~40 sI never concatenate URLs by hand. `Uri.https(authority, unencodedPath, queryParameters)` and `Uri.http` build a URI from parts, and the general `Uri(scheme:, host:, path:, queryParameters:, fragment:)` constructor covers everything else. The query map is typed `Map<String, dynamic>`, but the documentation requires each value to be `null`, a `String` or an `Iterable<String>`; an iterable repeats the key, and anything else, such as an `int`, fails with a type error at runtime, so write `'$nights'`. Keys and values are percent-encoded with spaces turned into `+`. Going the other way, `Uri.parse` throws a `FormatException` on invalid text, `Uri.tryParse` returns `null`, `queryParameters` gives an unmodifiable map with one value per key, and `queryParametersAll` keeps repeated keys. `Uri.encodeComponent` and `encodeFull` handle one-off strings.
code
dart · 17 linesUri availabilityUri(String city, DateTime checkIn, int nights) => Uri.https(
'api.example.com',
'/v1/availability',
{
'city': city, // 'São Paulo' is percent-encoded for you
'from': checkIn.toUtc().toIso8601String(),
'nights': '$nights', // an int here would fail at runtime
'amenity': ['wifi', 'parking'], // repeated key
},
);
void main() {
final uri = availabilityUri('São Paulo', DateTime.utc(2026, 10, 24), 3);
print(uri.queryParameters['city']); // São Paulo
print(uri.queryParametersAll['amenity']); // [wifi, parking]
print(Uri.tryParse('::Not valid URI::')); // null
}go deeper
Know to build URLs with Uri.https and a query map instead of string concatenation, and that values must be strings.
Explain the value contract (String, null, Iterable<String>), + for spaces, parse versus tryParse, and queryParameters versus queryParametersAll.
Catch double encoding, runtime type errors from dynamic maps, and lost repeated keys in review, and wrap URL building in one typed helper per API.
Put endpoint construction behind a typed client layer so no feature builds URLs itself, which keeps encoding rules and base hosts in one place.
## Why not string concatenation Building `'https://api.example.com/v1/availability?city=' + city` breaks as soon as the city is `São Paulo` or contains `&`. The `Uri` class in `dart:core` builds URIs from parts and encodes each part correctly, and it parses URI strings back into parts. ## Building | API | Use it for | |---|---| | `Uri.https(authority, [unencodedPath, queryParameters])` | the common HTTPS request URL | | `Uri.http(...)` | the same with the `http` scheme | | `Uri(scheme:, host:, port:, path:, query:, queryParameters:, fragment:)` | anything else, including a fragment or a custom scheme | | `uri.replace(queryParameters: {...})` | a copy with some parts changed | `Uri.https` takes the authority, host with an optional port or user info, then a path that it percent-encodes, so `'a b'` becomes `a%20b` and a literal `%` becomes `%25`. The path may be omitted. ## The query map's contract The parameter is typed `Map<String, dynamic>`, which the compiler cannot check further, so the rule lives in the documentation and the runtime: - A value must be **`null`, a `String` or an `Iterable<String>`**. - An **iterable** produces the key once per element, which is how APIs take repeated keys such as `amenity=wifi&amenity=parking`. - **`null`** or an empty iterable gives the key with no value. - Any other value, such as an `int`, a `bool` or a `DateTime`, is treated as an iterable and fails with a **type error at runtime**. Convert it first: `'$nights'`, `checkIn.toUtc().toIso8601String()`. - Every key and value is **percent-encoded** except unreserved characters, and **spaces become `+`**, the form-encoding convention. - You cannot pass both `query` and `queryParameters`; doing so throws an `ArgumentError`. ## Parsing and reading - `Uri.parse(text)` throws a `FormatException` when the text is not a valid URI or URI reference; `Uri.tryParse(text)` returns `null` instead. - `uri.scheme`, `host`, `port`, `path`, `pathSegments` and `fragment` expose the parts. - `uri.queryParameters` is an **unmodifiable** `Map<String, String>` with decoded values; if a key repeats, one of its values is chosen arbitrarily. - `uri.queryParametersAll` maps each key to **all** of its values. ## Encoding single strings When you only need to encode one piece, for example for a deep-link token: 1. `Uri.encodeComponent(s)` encodes everything that has special meaning in a URI, including `/`, `&`, `:`; use it for one path segment or value. 2. `Uri.encodeFull(s)` leaves the URI's structural characters alone and encodes the rest; use it for a whole URL that only has stray spaces. 3. `Uri.decodeComponent` and `Uri.decodeFull` reverse them. Prefer building with `Uri.https` over encoding by hand; the constructor chooses the right encoding per part. ## Reading an incoming deep link The same class parses links the app receives, for example a confirmation email linking to `https://book.example.com/stay/LIS-0042?guests=2&tag=a&tag=b`: - `uri.pathSegments` gives `['stay', 'LIS-0042']`, already split and decoded. - `uri.queryParameters['guests']` gives `'2'`, a string, so it still needs `int.tryParse`. - `uri.queryParametersAll['tag']` gives `['a', 'b']`. - A missing key returns `null` from the map, so every read needs a fallback. Routing packages parse links for you, but the values they hand over are these same decoded strings. ## A booking example Searching availability needs a city, a check-in instant and a number of nights, plus repeated amenities. With `Uri.https` the city `São Paulo` is encoded, the timestamp's colons are escaped, `nights` is converted to a string first, and the amenity list becomes two `amenity` entries. The resulting `Uri` is what HTTP client packages take as their URL argument; those packages are a separate topic. ## Mistakes interviewers look for - Concatenating user input into a URL string. - Passing an `int` or `DateTime` as a query value and meeting a runtime type error. - Reading `queryParameters` on a URL with repeated keys and losing values. - Using `Uri.parse` on user-typed text without handling `FormatException`, where `tryParse` fits. - Encoding a value twice, once by hand with `encodeComponent` and again by the constructor, producing `%2520`.
- What is the difference between Uri.encodeComponent and Uri.encodeFull?`encodeComponent` escapes every character with special meaning in a URI, including `/`, `:` and `&`, so it suits one path segment or query value. `encodeFull` leaves those structural characters alone and escapes the rest, so it suits a complete URL that only contains stray spaces. When building with `Uri.https` you need neither.
- A deep link arrives as ?tag=a&tag=b. What does uri.queryParameters['tag'] return?One of the two values, chosen arbitrarily, because `queryParameters` maps each key to a single string. Use `uri.queryParametersAll['tag']`, which returns `['a', 'b']`.
saying these in an interview costs you the question
- Uri.https converts int query values with toString() automatically
- Uri.parse returns null for invalid text
- queryParameters returns every value of a repeated key
- Encode values with Uri.encodeComponent before passing them to Uri.https
- Building URLs with string interpolation is fine if you trim the input