Từ khóa người dùng nhập có thể chứa dấu cách, dấu cộng và dấu &. Nếu nối thẳng từ khóa vào chuỗi URL, ký tự vốn thuộc dữ liệu có thể bị đọc thành dấu phân cách query. Uri.Builder giúp tách cấu trúc URI khỏi các giá trị cần được mã hóa.
Bài đối chiếu Android Uri và Uri.Builder ngày 05/11/2026. Mẫu dành cho module Android Kotlin, giả định minSdk từ 16; các phương thức builder được dùng có từ API 1. Code được rà thủ công theo API, chưa build với Android SDK hoặc chạy trên emulator. Các lệnh check là tiêu chí bạn có thể chạy trong môi trường Android, không phải kết quả instrumentation đã thu được.
Mã hóa từng thành phần, không mã hóa cả URI
Một URI có scheme, authority, path và query. Dấu & trong query có vai trò phân tách parameter; nhưng dấu & trong từ khóa “Flutter & Dart” là nội dung của một giá trị. Khi thêm giá trị qua appendQueryParameter, builder thực hiện mã hóa key và value trước khi thêm vào query.
Bạn truyền chuỗi dữ liệu gốc cho phương thức này. Không tự encode trước rồi lại đưa chuỗi đã encode vào appendQueryParameter. Cách làm đó có thể khiến dấu phần trăm trong biểu diễn mã hóa tiếp tục bị mã hóa, làm bên nhận đọc được nội dung khác với dữ liệu ban đầu.
1. Viết helper với cấu trúc URI cố định
Trong một file Kotlin thuộc module Android, thêm đoạn sau. Host example.invalid là miền dành cho minh họa, không phải endpoint tìm kiếm đang hoạt động:
import android.net.Uri
fun buildSearchUri(term: String, tags: List<String>): Uri {
require(term.isNotBlank()) { "Search term must not be blank" }
val builder = Uri.Builder()
.scheme("https")
.authority("example.invalid")
.appendPath("search")
.appendQueryParameter("q", term)
tags.forEach { tag ->
builder.appendQueryParameter("tag", tag)
}
return builder.build()
}
Helper cố định scheme và host, nhận term cùng danh sách tag như dữ liệu. Nó chỉ kiểm tra term không toàn khoảng trắng, không tự trim để tránh âm thầm đổi đầu vào. Nếu sản phẩm muốn bỏ khoảng trắng đầu cuối, thực hiện quyết định đó ở một bước rõ ràng trước khi gọi helper.
Mỗi tag được thêm thành một parameter cùng tên, không gộp bằng dấu phẩy. Đây là một hợp đồng query cụ thể: server phải hỗ trợ key lặp. Nếu API chỉ nhận một giá trị tag hoặc một JSON payload, thay thiết kế theo API ấy; builder không tự quyết định hợp đồng phía server.
2. Kiểm tra dữ liệu sau khi giải mã
Trong một Android instrumentation test hoặc điểm gọi chẩn đoán của app, có thể kiểm tra helper bằng đoạn sau:
fun verifySearchUri() {
val term = "Flutter & Dart + tiếng Việt"
val tags = listOf("mobile dev", "a+b")
val uri = buildSearchUri(term, tags)
check(uri.scheme == "https")
check(uri.host == "example.invalid")
check(uri.path == "/search")
check(uri.getQueryParameter("q") == term)
check(uri.getQueryParameters("tag") == tags)
check(uri.getQueryParameter("missing") == null)
}
Điểm cần kiểm tra là term và tags sau giải mã còn đúng dữ liệu ban đầu. Chỉ thấy một chuỗi URL “có vẻ hợp lệ” chưa chứng minh dấu & không làm tách sai parameter. getQueryParameters giúp đọc tất cả giá trị của key lặp; getQueryParameter không thay thế việc kiểm tra cả danh sách.
Không gửi request mạng trong demo này. Việc dựng và đọc URI thành công chưa chứng minh API nhận đúng key, cho phép tag lặp hay có endpoint tương ứng. Khi tích hợp thật, thêm test với parser query phía server và các giá trị tiếng Việt tương tự.
Khi đã có URI nền
Nếu phải giữ query hiện có, dùng buildUpon trên URI nền đã được ứng dụng kiểm soát rồi thêm parameter. Nhớ rằng append nghĩa là thêm, không tự thay giá trị cũ cùng key. Một URI đã chứa q có thể nhận thêm q thứ hai; cần xác định quy tắc thay thế thay vì hy vọng builder tự gộp.
Không đưa nguyên một URL vào appendPath với kỳ vọng builder nhận ra các thành phần của nó. Phương thức path và query phục vụ những vị trí khác nhau. Các phương thức nhận nội dung đã mã hóa cũng có hợp đồng khác; chỉ dùng khi bạn thật sự đang có dữ liệu encoded phù hợp.
Lỗi thường gặp và bước kiểm tra tiếp
Một test JVM thuần có thể import tên Uri nhưng chưa có implementation Android thực thi đầy đủ. Chạy các check bằng môi trường test Android phù hợp của project; không dùng một phép encode bằng thư viện khác làm bằng chứng rằng Uri.Builder đã chạy đúng trên thiết bị.
Thêm ca term rỗng, chỉ khoảng trắng, có #, dấu phần trăm và tag rỗng theo quy tắc API. Helper mẫu không giới hạn độ dài hay quyết định tag rỗng có hợp lệ không. Giữ những yêu cầu ấy ở lớp validation riêng, rồi kiểm tra rằng dữ liệu đã được chấp nhận được bảo toàn qua vòng dựng và giải mã URI.

