{"id":13536671,"url":"https://github.com/corbella83/kotliny.network","last_synced_at":"2025-04-02T03:30:55.918Z","repository":{"id":178267359,"uuid":"659762854","full_name":"corbella83/kotliny.network","owner":"corbella83","description":"Kotliny Network is a simple, powerful and lightweight Kotlin Multiplatform Network Client.","archived":false,"fork":false,"pushed_at":"2023-07-03T14:39:14.000Z","size":181,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2024-11-03T01:33:20.496Z","etag":null,"topics":["android","java","kotlin","multiplatform","network","okhttp"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/corbella83.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2023-06-28T14:00:57.000Z","updated_at":"2024-10-06T07:50:22.000Z","dependencies_parsed_at":"2024-01-14T02:39:09.564Z","dependency_job_id":"280e4ca1-c4a4-4470-911d-5e133b35b5d4","html_url":"https://github.com/corbella83/kotliny.network","commit_stats":null,"previous_names":["corbella83/kotliny.network"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/corbella83%2Fkotliny.network","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/corbella83%2Fkotliny.network/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/corbella83%2Fkotliny.network/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/corbella83%2Fkotliny.network/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/corbella83","download_url":"https://codeload.github.com/corbella83/kotliny.network/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246750947,"owners_count":20827807,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","java","kotlin","multiplatform","network","okhttp"],"created_at":"2024-08-01T09:00:47.012Z","updated_at":"2025-04-02T03:30:55.429Z","avatar_url":"https://github.com/corbella83.png","language":"Kotlin","funding_links":[],"categories":["Libraries"],"sub_categories":["Network"],"readme":"[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.kotliny.network/kotliny-network/badge.svg)](https://search.maven.org/artifact/com.kotliny.network/kotliny-network)\n[![Kotlin](https://img.shields.io/badge/kotlin-1.8.22-blue.svg?logo=kotlin)](http://kotlinlang.org)\n[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0)\n\nKotliny - Network Client\n==========\n\nKotliny Network is a simple, powerful and lightweight Kotlin Multiplatform Network Client.\n\nGet it with Gradle:\n\n```groovy\nimplementation(\"com.kotliny.network:kotliny-network-client:1.0.0\")\n```\n\nTo create a basic client you will need a folder that will be used by the client to save temporary files.\n\n```kotlin\nval folder = folderOf(\"/some/temporary/folder\")\n\nval client = NetworkClient(folder)\n```\n\nSome options can also be provided when creating the `NetworkClient`:\n\n```kotlin\nval client = NetworkClient(folder) {\n    setUserAgent(\"MyAgent\") // Sets the User-Agent header to use by all requests\n    setLoggerEnabled()  // Enables logging all requests and responses.\n    setCacheEnabled()  // Enables cache according to the received headers (\"Cache-Control\").\n    setCookiesEnabled()  // Enables cookies, using the corresponding headers (\"Set-Cookie\", \"Cookie\")\n}\n```\n\n### Urls\n\nTo provide urls, you can use the `urlOf` methods that will parse the URL:\n\n```kotlin\nval url: HttpUrl = urlOf(\"http://www.domain.com/some/path?some=query\")\n\nval url: HttpUrl = urlOf(\"http\", \"www.api.domain.com\", \"some/path\", listOf(\"some\" to \"query\"))\n```\n\nThese methods will throw an exception if something is wrong. But you can use the null-safe equivalent instead:\n\n```kotlin\nval url: HttpUrl? = urlOrNullOf(\"http://www.api.domain.com/some/path?some=query\")\n\nval url: HttpUrl? = urlOrNullOf(\"http\", \"www.api.domain.com\", \"some/path\", listOf(\"some\" to \"query\"))\n```\n\nRequest\n--------\nThe body of a request is represented by an `HttpContent` and can be one of the following types:\n\n### HttpContent.Empty\n\nThis is the body that is used when the request doesn't need a body.\n\n```kotlin\nclient.launch(HttpMethod.GET, url, HttpContent.Empty())\n\n// To make a request with headers:\nval emptyContent = HttpContent.Empty(headersOf(\"My-Token\" to \"123456\"))\nclient.launch(HttpMethod.GET, url, emptyContent)\n```\n\n### HttpContent.Single\n\nThis is the body that is used when sending a single Content-Type.\n\nEvery Content-Type is represented by an `HttpContentData`.\n\nFor example, to send a json (application/json):\n\n```kotlin\nval jsonData = HttpContentData.Json(\"{ \\\"first\\\": \\\"one\\\", \\\"second\\\": \\\"two\\\" }\")\nclient.launch(HttpMethod.POST, url, HttpContent.Single(jsonData))\n\n// To make a request with headers:\nval singleContent = HttpContent.Single(jsonData, headersOf(\"My-Token\" to \"123456\"))\nclient.launch(HttpMethod.POST, url, singleContent)\n```\n\nAnd to send an image (image/jpeg):\n\n```kotlin\nval imageData = HttpContentData.Image(\"jpeg\", File(...))\nclient.launch(HttpMethod.POST, url, HttpContent.Single(imageData))\n\n// To make a request with headers:\nval singleContent = HttpContent.Single(imageData, headersOf(\"My-Token\" to \"123456\"))\nclient.launch(HttpMethod.POST, url, singleContent)\n```\n\n### HttpContent.Mix and HttpContent.Form\n\nThis is the body that is used when sending multiple Content-Types under the same request. The so called MULTIPART.\n`Mix` is the default usage (multipart/mixed), while `Form` is when the contents are disposed by a form (multipart/form-data)\n\nFor example, to send a multipart form with a json (application/json) and an image (image/jpeg):\n\n```kotlin\nval jsonData = HttpContentData.Json(\"{ \\\"first\\\": \\\"one\\\", \\\"second\\\": \\\"two\\\" }\")\nval imageData = HttpContentData.Image(\"jpeg\", File(...))\n\nclient.launch(\n    HttpMethod.POST, url, HttpContent.Form(\n        mapOf(\n            \"properties\" to HttpContent.Single(jsonData, /*Some Optional Headers*/),\n            \"profile\" to HttpContent.Single(imageData, /*Some Optional Headers*/),\n        ),\n    )\n)\n```\n\nResponse\n--------\nThe response is represented as an `HttpResult\u003cHttpContent, HttpContent\u003e`. Where `HttpContent` is the same model as defined in the request. The result can be:\n\n* `Success` -\u003e If the response code is 2xx.\n* `Error` -\u003e If the response code is 4xx or 5xx\n* `Failure` -\u003e If some unexpected behavior has happened\n\nThe 3xx response codes are handled internally and should never arrive at this point.\n\n```kotlin\nval result: HttpResult\u003cHttpContent, HttpContent\u003e = client.launch(HttpMethod.GET, url, HttpContent.Empty())\n```\n\nAn `HttpResult` can be mapped into anything else:\n\n```kotlin\nval result1: HttpResult\u003cMyModel, HttpContent\u003e = result.mapSuccess {\n    // Transform from HttpContent response to MyModel\n}\n\nval result2: HttpResult\u003cHttpContent, MyErrorModel\u003e = result.mapError {\n    // Transform from HttpContent response to MyErrorModel\n}\n\nval result3: HttpResult\u003cHttpContent, HttpContent\u003e = result.mapFailure {\n    // Transform the exception into another exception\n}\n```\n\nOr we can just fold the `HttpResult` into another type\n\n```kotlin\nval kotlinResult: Result\u003cPair\u003cInt, String\u003e\u003e = result.fold(\n    onSuccess = { Result.success(code to response.toString()) },\n    onError = { Result.success(code to response.toString()) },\n    onFailure = { Result.failure(exception) }\n)\n```\n\nAlso, if we are just interested in a particular result type, we can just get it:\n\n```kotlin\nval result1: MyModel? = result.successOrNull\n\nval result2: MyErrorModel? = result.errorOrNull\n\nval result3: Throwable? = result.failureOrNull\n```\n\nApi Caller\n--------\nIn order to simplify the integration with an API, we can use the `ApiCaller` extension:\n\n```groovy\nimplementation(\"com.kotliny.network:kotliny-network-api-caller:1.0.0\")\n```\n\nTo get an instance of an `ApiCaller` you will need the client responsible for making the requests, the base URL of the API service, and an `APISerializer` instance (JSON or XML) that will be used by default to serialize / deserialize objects.\nAdditionally, the library provides a default implementation of a JSON serializer.\n\n```groovy\nimplementation(\"com.kotliny.network:kotliny-network-serializer-json:1.0.0\") // APISerializer that uses the kotlinx.serialization library (multiplatform)\nor\nimplementation(\"com.kotliny.network:kotliny-network-serializer-gson:1.0.0\") // APISerializer that uses the gson library (only for jvm)\n```\n\n```kotlin\nval apiCaller = ApiCaller(client, folder, urlOf(\"api.domain.com\"), JsonApiSerializer())\n```\n\nIts usage is quite simple. The `ApiCaller` will automatically handle Content-Types and provide you with the expected result.\nIf the received Content-Type cannot be represented as the expected type, an `HttpResult.Failure` will be returned. \nFor instance, if you expect a JSON but receive an \"image/jpeg\", an `HttpResult.Failure` will be returned.\n\n```kotlin\n// Expect a JSON model when success or error\nval result: HttpResult\u003cMyModel, MyErrorModel\u003e = apiCaller.get\u003cMyModel, MyErrorModel\u003e(\"relative/path/json\")\n\n// Expect a file when success, and a JSON when error\nval result: HttpResult\u003cFile, MyErrorModel\u003e = apiCaller.get\u003cFile, MyErrorModel\u003e(\"relative/path/image\")\n\n// Expect a string when success (might be raw json, hml, etc. any content capable of being represented as a string), and the unhandled content when error\nval result: HttpResult\u003cString, HttpContent\u003e = apiCaller.get\u003cMyModel, HttpContent\u003e(\"relative/path/json\")\n```\n\nYou can also define your own rule to handle a particular response model. Imagine that the previous MyErrorModel is a sealed class and needs to be parsed different depending on the response code:\n\n```kotlin\nclass ErrorContentHandler(serializer: ApiSerializer) : ContentHandler\u003cMyErrorModel\u003e {\n    private val clientSerializable = SerializableContentHandler(fullType\u003cMyErrorModel.Client\u003e(), serializer)\n    private val serverSerializable = SerializableContentHandler(fullType\u003cMyErrorModel.Server\u003e(), serializer)\n\n    override val type: FullType\u003cMyErrorModel\u003e = fullType\u003cMyErrorModel\u003e()\n\n    override fun convert(code: Int, content: HttpContent): Result\u003cMyErrorModel\u003e {\n        return if (code in 400..499) {\n            clientSerializable.convert(code, content)\n        } else {\n            serverSerializable.convert(code, content)\n        }\n    }\n}\n\n// Add it into the apiCaller instance that you have\napiCaller.addContentHandler(ErrorContentHandler(jsonSerializable))\n```\n\n### Headers and Queries\n\nTo make a request with custom queries and headers:\n\n```kotlin\nval apiCaller = apiCaller.get\u003cMyModel, MyErrorModel\u003e(\"relative/path/json\") {\n    // Set header\n    setHeader(\"My-Auth\", \"TOKEN\")\n\n    // Set simple query\n    setQuery(\"first\", \"one\")\n\n    // Set query list\n    setQuery(\"letter\", listOf(\"a\", \"b\", \"c\"))\n}\n```\n\nIf there are some queries or headers that must be used along all the requests, set them to the apiCaller instance\n\n```kotlin\n// Set common header\napiCaller.setCommonHeader(\"My-Auth\", \"TOKEN\")\n\n// Set common lazy header.\napiCaller.setCommonQuery(\"My-Auth\") { \"TOKEN\" }\n\n// Set common query\napiCaller.setCommonQuery(\"first\", \"one\")\n\n// Set common lazy query.\napiCaller.setCommonQuery(\"first\") { \"one\" }\n```\n\nEngines\n--------\nBy default, a `NetworkClient` instance is using a java8 engine (for java) and URLSession engine (for ios) to perform the requests.\n\nIf using java8 and Android, PATCH is not officially supported and needs to have this Proguard rule in order for it to work.\n\n```proguard\n-keep class * implements java.net.HttpURLConnection { *; }\n```\n\nThe library provides 2 more engines to be used in JVM, but you can always write your own implementation of an engine by extending `HttpEngine` if you need.\n\n```groovy\n// To use a HttpClient defined in java since JMV11 \nimplementation(\"com.kotliny.network:kotliny-network-engine-jvm11:1.0.0\")\n\n// To use an OkHttpClient\nimplementation(\"com.kotliny.network:kotliny-network-engine-okhttp:1.0.0\")\n```\n\n```kotlin\nval client = NetworkClient(folder) {\n    setEngine(Java11HttpEngine())\n}\n\nval client = NetworkClient(folder) {\n    setEngine(OkHttpEngine())\n}\n```\n\nTesting\n--------\nThe library also provides a simple utilities in order to simplify testing the network layer, without actually making the requests.\n\n```groovy\nimplementation(\"com.kotliny.network:kotliny-network-engine-test:1.0.0\")\n```\n\nThere are mainly three types of engines that can be used for testing:\n\n### EchoHttpEngine\n\nThis engine simply takes the request and returns it as the response. If you need to test how a certain response is handled by your app, simply make a request with that response, and you'll get it back.\nBy default, the response code is a 200, but you can use the extra header EchoHttpEngine.RESPONSE_CODE to define another one.\n\n```kotlin\nval client = NetworkClient(folder) {\n    setEngine(EchoHttpEngine())\n}\n\n// Using the client directly\nval content = HttpContent.Single(HttpContentData.Text(\"Hi There\"))\nval result1: HttpResult\u003cHttpContent, HttpContent\u003e = client.launch(HttpMethod.GET, url, content)\n\n// Or using the ApiCaller\nval result2: HttpResult\u003cString, String\u003e = apiCaller.get(\"path\", HttpContentData.Text(\"Hi There\"))\n```\n\n### MockHttpEngine\n\nThis engine mocks a certain response, given a certain request. When using this engine, every unmocked request will throw an exception.\n\n```kotlin\nval engine = MockHttpEngine()\nval client = NetworkClient(folder) {\n    setEngine(engine)\n}\n\nengine.setResponseFor(\"GET\", \"http://www.domain.com/path\") {\n    NetworkResponse(403, listOf(), \"Hi there\".source())\n}\n\n// Using the client directly\nval result1: HttpResult\u003cHttpContent, HttpContent\u003e = client.launch(HttpMethod.GET, url, HttpContent.Empty())\n\n// Or using the ApiCaller\nval result2: HttpResult\u003cString, String\u003e = apiCaller.get(\"path\", HttpContentData.Empty())\n```\n\n### LocalHttpEngine\n\nThis engine is a very simple implementation of a functional server. You can POST elements that latter can be retrieved by GET and can be removed by DELETE. To simplify things, this local engine is only working with `HttpContentData.Text`.\n\n```kotlin\nval client = NetworkClient(folder) {\n    setEngine(LocalHttpEngine())\n}\n\nval id: Long = apiCaller.post(\"data\", HttpContentData.Text(\"One\"))\n\nval result: String? = apiCaller.get\u003cString\u003e(\"data/$id\").successOrNull // Result is \"One\"\n\napiCaller.delete(\"data/$id\")\n\nval result: String? = apiCaller.get\u003cString\u003e(\"data/$id\").successOrNull // Result is null due to 404 Not Found\n```\n\nLicense\n-------\n\n    Copyright 2023 Pau Corbella\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n    ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcorbella83%2Fkotliny.network","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcorbella83%2Fkotliny.network","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcorbella83%2Fkotliny.network/lists"}