Available on Maven Central:
implementation("ch.ubique.kmp:network:1.1.1")You may find the current version and version history in the Releases list.
This networking library builds on Ktor.
On how to use Ktor, have a look at the following resources:
- Creating a cross-platform mobile application using Ktor
- Creating and configuring a client
- Making requests with Ktor
An application-level plugin for Ktor implementing a disk cache, supporting the major HTTP caching mechanisms as well as the custom cache rules specified by Ubique.
import ch.ubique.libs.ktor.plugins.Ubiquache
val client = HttpClient() {
install(Ubiquache)
}On Android, the Ubiquache plugin needs to be initialized before installing it in the Ktor client, e.g. in Application.onCreate():
UbiquacheConfig.init(context)If you need multiple independent caches, you can configure each plugin instance with a distinct name. Furthermore, you can set the maximum cache size in bytes:
Code example
val client = HttpClient() {
install(Ubiquache) {
name = "my-cache"
maxSize = 256 * 1024 * 1024 // 256 MB
}
}The disk-level cache supports the cache control mechanisms as defined by following HTTP headers:
Request HTTP headers
Cache-Control: no-cache– The response will not be loaded from cache and forces a network request.Cache-Control: no-store– The response will not be stored to cache, but may return a stored response from cache if it's valid.Cache-Control: only-if-cached– Prevent a network request. Fails with status code 504 if there is no valid cached response.
Response HTTP headers
Expires: <date>X-Best-Before: <date>– and variants; synonymous withExpires.X-Next-Refresh: <date>– and variantsETag: <tag>,Last-Modified: <date>Cache-Control: max-age=<seconds>Cache-Control: no-cacheCache-Control: no-store
A request is uniquely identified by the following attributes. If any of these values differ, the request is handled and cached separately.
- URL
- HTTP method
- Any Accept-* headers
- Authorization header
By obtaining the plugin instance:
val ubiquache = httpClient.plugin(Ubiquache)you can access basic cache information and perform cleanup operations:
ubiquache.clearCache()– Removes all cached responses.ubiquache.clearCache(url)– Removes the cached response for a specific URL.ubiquache.usedCacheSize()– Current cache size in bytes.ubiquache.maxCacheSize()– Maximum cache size in bytes.
ktorStateFlow() creates a StateFlow that, if it has active observers, executes a request and automatically refreshes
according to the response cache headers, i.e. reloads if the response needs to be refreshed or is expired.
This requires the Ktor request to forward a Cache-Control header and return a Response with the desired result type:
val stateflow = ktorStateFlow<MyModel> { cacheControl ->
client.get(url) { cacheControl(cacheControl) }
}The StateFlow holds the current state which is either Loading, Result containing the data, or Error with an exception and a retry function.
stateflow.collect { state ->
when (state) {
is RequestState.Loading -> { } // loading state
is RequestState.Result -> { state.data } // result state
is RequestState.Error -> { state.exception; state.retry() } // error state
}
}In case of an error, the ktorStateFlow stops and has to be restarted manually, either with errorState.retry() or with stateflow.forceReload().
Example of a ktorStateFlow with a changing request parameter, e.g. a filter.
Setting the field exampleFilter automatically forces a reload with the new value:
Code example
var exampleFilter: String = "default"
set(value) {
field = value
stateflow.reload()
}
val stateflow = ktorStateFlow<summary> { cacheControl ->
client.get(url) {
url { parameter("filter", exampleFilter) }
cacheControl(cacheControl)
}
}Or using a StateFlow as a value source instead, with flatMapLatestToKtorStateFlow():
Code example
val exampleFilter = MutableStateFlow("default")
val requestStateFlow = exampleFilter.flatMapLatestToKtorStateFlow { filter ->
ktorStateFlow<MyModel> { cacheControl ->
client.get(url) {
url { parameter("filter", filter) }
cacheControl(cacheControl)
}
}
}Example of a method returning a new ktorStateFlow instance for different but constant parameter values:
Code example
fun stateflow(exampleId: String) = ktorStateFlow<MyModel> { cacheControl ->
client.get(url) {
url { parameter("exampleId", exampleId) }
cacheControl(cacheControl)
}
}HTTP client plugin to add the Accept-Language HTTP header. Either with a fixed language code, or a system dependent language list.
val client = HttpClient() {
install(AcceptLanguage) {
language = "de" // static ...
languageProvider = { "de" } // ... or callback
}
}HTTP client plugin to add the User-Agent HTTP header, containing basic system and app information.
val client = HttpClient() {
AppUserAgent()
}Most features of this library can be implemented with test-driven development using unit tests with a mock webserver instance.
To test any changes locally in an app, you can either include the library via dependency substitution in an application project, or deploy a build to your local maven repository and include that from any application:
-
Define a unique custom version by setting the
VERSION_NAMEvariable in thegradle.propertiesfile. -
Deploy the library artifact by running
./gradlew publishToMavenLocal -
Reference the local maven repository in your application's build script:
repositories { mavenLocal() } -
And apply the local library version:
dependencies { implementation("ch.ubique.kmp:network:$yourLocalVersion") }
Unit tests and coverage reports are run on the JVM target by default. See also workflows for Test and Coverage.
Create a Release,
setting the Tag to the desired version prefixed with a v.
Each release on Github will be deployed to Maven Central.
- Group:
ch.ubique.kmp - Artifact:
network - Version:
major.minor.revision