Building a Robust URL Builder in Kotlin with Extensions

MaKB
4 min readNov 20, 2024

--

Photo by Remotar Jobs on Unsplash

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! 😊

--

--

MaKB
MaKB

Written by MaKB

Experienced software engineer with demonstrated history of working in telecommunications industry along with many other sectors like education, e-commerce etc.

No responses yet