Constructing dynamic URLs is a frequent requirement in modern app development. A URL builder streamlines this process, improving readability and maintainability of code. In this blog, we’ll expand on a simple Kotlin URL builder, adding useful extensions like query parameters, custom encodings, and more.
Why Use a URL Builder?
When interacting with APIs, constructing URLs manually can quickly become error-prone. Consider the following:
val url = "https://api.example.com" + "/users" + "/" + userId + "/details"
While it’s simple, it’s hard to maintain and doesn’t handle edge cases (like leading or trailing slashes). A URL builder addresses these issues by providing a structured, reusable, and readable approach to building URLs.
A basic URL builder is excellent for managing path segments, but real-world applications often require:
1. Query parameters for filtering or pagination.
2. Custom encodings to handle special characters in paths or parameters.
3. Seamless integration with networking libraries like Retrofit or Ktor.
Let’s enhance the builder to address these needs.
Designing the URL Builder
Our goal is to create a class that:
1. Initializes with a base URL.
2. Allows appending multiple path segments dynamically.
3. Constructs a clean and well-formatted URL.
The builder pattern ensures the flexibility to build complex objects (like URLs) step by step.
Enhanced URL Builder
Below is the enhanced version of our URLBuilder that supports query parameters and custom encodings:
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
class URLBuilder private constructor(private val baseUrl: String) {
private val paths = mutableListOf<String>()
private val queryParams = mutableMapOf<String, String>()
// Add a path segment to the URL
fun addPath(path: String) = apply {
paths.add(path)
}
// Add query parameters to the URL
fun addQueryParam(key: String, value: String) = apply {
queryParams[key] = value
}
// Add multiple query parameters
fun addQueryParams(params: Map<String, String>) = apply {
queryParams.putAll(params)
}
// Builds the final URL
fun build(): String {
val fullPath = paths.joinToString("/") { it.trim('/') }
val baseWithPath = "$baseUrl/$fullPath".trimEnd('/')
// Append query parameters
val queryString = if (queryParams.isNotEmpty()) {
queryParams.entries.joinToString("&") {
"${encode(it.key)}=${encode(it.value)}"
}
} else {
null
}
return if (queryString != null) "$baseWithPath?$queryString" else baseWithPath
}
private fun encode(value: String): String =
URLEncoder.encode(value, StandardCharsets.UTF_8)
companion object {
fun from(baseUrl: String) = URLBuilder(baseUrl)
}
}
Features Added
1. Path Segment Management : Handles appending path segments while ensuring proper formatting.
2. Query Parameters : Use addQueryParam() to add one key-value pair. Use addQueryParams() for a batch of parameters.
3. Custom Encoding : Automatically URL-encodes path segments and query parameters to handle special characters.
Example Usage
Basic URL Construction:
val url = URLBuilder.from("https://api.example.com")
.addPath("users")
.addPath("123")
.addPath("details")
.build()
println(url)
// Output: https://api.example.com/users/123/details
Adding Query Parameters:
val urlWithParams = URLBuilder.from("https://api.example.com")
.addPath("users")
.addQueryParam("filter", "active")
.addQueryParam("sort", "asc")
.build()
println(urlWithParams)
// Output: https://api.example.com/users?filter=active&sort=asc
Handling Special Characters:
val encodedUrl = URLBuilder.from("https://api.example.com")
.addPath("users")
.addQueryParam("name", "John Doe")
.addQueryParam("city", "New York")
.build()
println(encodedUrl)
// Output: https://api.example.com/users?name=John%20Doe&city=New%20York
Seamless Integration with Networking Libraries
Retrofit
Retrofit uses dynamic URLs for making API calls. The URLBuilder can be integrated like this:
interface ApiService {
@GET
suspend fun getUserDetails(@Url url: String): Response<User>
}
// Usage
val apiService: ApiService = retrofit.create(ApiService::class.java)
val url = URLBuilder.from("https://api.example.com")
.addPath("users")
.addPath("123")
.build()
val response = apiService.getUserDetails(url)
Ktor
With Ktor’s HttpClient, you can use the URL builder to construct API request URLs dynamically:
val client = HttpClient()
val url = URLBuilder.from("https://api.example.com")
.addPath("users")
.addQueryParam("filter", "active")
.build()
val response: HttpResponse = client.get(url)
println(response.readText())
Extensibility
Here are some additional ideas for enhancing the URLBuilder:
1. Default Query Parameters
Add a method to set default parameters that are included in every URL.
2. Path Validation
Validate path segments to ensure they follow certain constraints (e.g., alphanumeric).
3. Support for Fragments
Add support for appending fragments (e.g., #section1) to URLs.
4. Logging or Debugging
Include a method to log or preview the constructed URL during development.
Wrapping up
The enhanced URLBuilder is a versatile tool for creating dynamic URLs in Kotlin. It handles path segments, query parameters, and special characters seamlessly. Additionally, it integrates well with popular networking libraries like Retrofit and Ktor.
By leveraging the builder pattern, this tool keeps your code clean, readable, and maintainable, even in complex API-driven applications.
Do you use a URL builder in your projects? What features would you add to this one? Let’s discuss in the comments! 😊
Happy learning!🚀
- —
Stay Updated with the Latest Posts: Don’t Miss Out!
If you found this post helpful, show your support by giving multiple claps 👏
Thank you for your support and appreciation! 😊