Created
May 17, 2026 16:05
-
-
Save faruktoptas/71bacb2e406c73171e3b746f66372d56 to your computer and use it in GitHub Desktop.
Ktor setup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| sourceSets { | |
| commonMain.dependencies { | |
| val ktorVersion = "3.5.0" // Always check for the latest Ktor 3.x version | |
| implementation("io.ktor:ktor-client-core:$ktorVersion") | |
| implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") | |
| implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") | |
| implementation("io.ktor:ktor-client-logging:$ktorVersion") | |
| implementation("io.ktor:ktor-client-auth:$ktorVersion") | |
| } | |
| androidMain.dependencies { | |
| implementation("io.ktor:ktor-client-okhttp:$ktorVersion") | |
| } | |
| iosMain.dependencies { | |
| implementation("io.ktor:ktor-client-darwin:$ktorVersion") | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| val httpClient = HttpClient(CIO) { | |
| // 1. Base URL & Default Request Configurations | |
| defaultRequest { | |
| url("https://api.example.com/v1/") | |
| header(HttpHeaders.ContentType, ContentType.Application.Json) | |
| } | |
| // 2. JSON Serialization / Deserialization | |
| install(ContentNegotiation) { | |
| json(Json { | |
| ... | |
| }) | |
| } | |
| // 3. Network Logging | |
| install(Logging) { | |
| logger = Logger.DEFAULT | |
| level = LogLevel.ALL // Options: NONE, HEADERS, BODY, ALL | |
| } | |
| // 4. Timeout Management | |
| install(HttpTimeout) { | |
| requestTimeoutMillis = 15000 | |
| connectTimeoutMillis = 15000 | |
| socketTimeoutMillis = 15000 | |
| } | |
| // 5. Automatic Authentication (Bearer Token Interceptor) | |
| install(Auth) { | |
| bearer { | |
| loadTokens { | |
| // Fetch cached tokens from secure storage | |
| val jwt = tokenHolder.getToken() | |
| val refreshToken = tokenHolder.getRefreshToken() | |
| if (token.isNotEmpty()){ | |
| BearerTokens(jwt, refreshToken) | |
| } | |
| } | |
| refreshTokens { | |
| // Triggered automatically whenever a 401 Unauthorized is received | |
| val newTokens = fetchNewTokens() | |
| BearerTokens(newTokens.accessToken, newTokens.refreshToken) | |
| } | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| install(ContentNegotiation) { | |
| json(Json { | |
| // Ignore extra fields sent by the backend | |
| ignoreUnknownKeys = true | |
| // Accepts malformed JSON JSON (e.g. unquoted keys) | |
| isLenient = true | |
| // Formats the raw string printout for your Logging plugin | |
| prettyPrint = true | |
| // Fallback to default values defined in your data class if the key is missing | |
| coerceInputValues = true | |
| // Force sending default properties in POST/PUT requests | |
| encodeDefaults = true | |
| // Strict null payload checking for maximum type safety | |
| explicitNulls = true | |
| }) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // data classes | |
| @Serializable | |
| data class User(val id: Int, val name: String, val email: String) | |
| @Serializable | |
| data class UserRequest(val name: String, val email: String) | |
| // send request | |
| suspend fun getUsers(): List<User> { | |
| return httpClient.get("users").body() | |
| } | |
| // GET with Query Parameters | |
| suspend fun searchUsers(query: String): List<User> { | |
| return httpClient.get("users/search") { | |
| parameter("query", query) | |
| }.body() | |
| } | |
| // POST | |
| suspend fun createUser(userRequest: UserRequest): User { | |
| return httpClient.post("users") { | |
| setBody(userRequest) | |
| }.body() | |
| } | |
| // MULTIPART FORM DATA | |
| import io.ktor.client.request.forms.* | |
| suspend fun uploadProfilePicture(imageBytes: ByteArray) { | |
| httpClient.post("user/avatar") { | |
| setBody(MultiPartFormDataContent( | |
| formData { | |
| append("description", "Profile Picture") | |
| append("image", imageBytes, Headers.build { | |
| append(HttpHeaders.ContentType, "image/jpeg") | |
| append(HttpHeaders.ContentDisposition, "filename=\"avatar.jpg\"") | |
| }) | |
| } | |
| )) | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| suspend fun safeApiCall() { | |
| try { | |
| val result = httpClient.get("protected-data").body<String>() | |
| } catch (e: RedirectResponseException) { // 3xx Errors | |
| println("Redirect Error: ${e.response.status.description}") | |
| } catch (e: ClientRequestException) { // 4xx Errors (e.g., 404, 403) | |
| println("Client Error: ${e.response.status.description}") | |
| } catch (e: ServerResponseException) { // 5xx Errors (e.g., 500) | |
| println("Server Error: ${e.response.status.description}") | |
| } catch (e: Exception) { // No internet connection, timeout, etc. | |
| println("An unexpected error occurred: ${e.message}") | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| val httpClient = HttpClient { | |
| // Intercepting Outgoing Requests (Equivalent to a Request Interceptor) | |
| sendPipeline.intercept(HttpSendPipeline.State) { | |
| // 'context' represents the HttpRequestBuilder | |
| val languageTag = "en-US" // This could come from a shared multiplatform settings library | |
| context.header("X-App-Language", languageTag) | |
| context.header("X-Platform", "KMP-Mobile") | |
| proceed() // Continues the pipeline execution | |
| } | |
| // Intercepting Incoming Responses (Equivalent to a Response Interceptor) | |
| receivePipeline.intercept(HttpReceivePipeline.State) { response -> | |
| // Inspect headers, log specific events, or trigger local actions | |
| if (response.status == HttpStatusCode.MaintenanceMode) { | |
| // Trigger a global UI event to show a maintenance screen | |
| } | |
| proceed(response) | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // retry | |
| install(HttpRequestRetry) { | |
| maxRetries = 3 // Retry up to 3 times | |
| retryIf { request, response -> | |
| response.status == HttpStatusCode.ServiceUnavailable // Only retry on 503 errors | |
| } | |
| delayMillis { retry -> | |
| retry * 2000L // Exponential backoff: 2s, 4s, 6s... | |
| } | |
| modifyRequest { request -> | |
| request.headers.append("X-Retry-Count", retryCount.toString()) | |
| } | |
| } | |
| // progress | |
| // Tracking Download Progress | |
| val response = client.get("https://example.com/large-asset.zip") { | |
| onDownload { bytesSentTotal, contentLength -> | |
| val progress = (bytesSentTotal.toDouble() / contentLength) * 100 | |
| println("Download progress: $progress%") | |
| } | |
| } | |
| // Tracking Upload Progress | |
| client.post("upload-video") { | |
| setBody(videoByteArray) | |
| onUpload { bytesSentTotal, contentLength -> | |
| println("Uploaded: $bytesSentTotal bytes out of $contentLength") | |
| } | |
| } | |
| // type safe resources | |
| // Define your nested endpoint structure | |
| @Resource("/users") | |
| class UsersResource { | |
| @Resource("{id}") | |
| class Id(val parent: UsersResource = UsersResource(), val id: Int) | |
| } | |
| // Usage in your service (No strings required, zero risk of typos) | |
| val user = client.get(UsersResource.Id(id = 42)).body<User>() | |
| // unit testing | |
| val mockEngine = MockEngine { request -> | |
| respond( | |
| content = """{"id":1,"name":"John Doe","email":"john@example.com"}""", | |
| status = HttpStatusCode.OK, | |
| headers = headersOf(HttpHeaders.ContentType, "application/json") | |
| ) | |
| } | |
| val testClient = HttpClient(mockEngine) { | |
| install(ContentNegotiation) { json() } | |
| } | |
| // Now use testClient inside your tests exactly like your real HttpClient! |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment