
Enhances HTTP client capabilities with caching, language, and user-agent plugins. Utilizes StateFlow for dynamic request handling, supporting cache control and management, and error recovery.
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:
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:
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:
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.Expires: <date>X-Best-Before: <date> – and variants; synonymous with Expires.X-Next-Refresh: <date> – and variantsETag: <tag>, Last-Modified: <date>
Cache-Control: max-age=<seconds>Cache-Control: no-cacheCache-Control: no-storeA request is uniquely identified by the following attributes. If any of these values differ, the request is handled and cached separately.
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:
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():
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:
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_NAME variable in the gradle.properties file.
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.
ch.ubique.kmp
network
major.minor.revision
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:
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:
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:
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.Expires: <date>X-Best-Before: <date> – and variants; synonymous with Expires.X-Next-Refresh: <date> – and variantsETag: <tag>, Last-Modified: <date>
Cache-Control: max-age=<seconds>Cache-Control: no-cacheCache-Control: no-storeA request is uniquely identified by the following attributes. If any of these values differ, the request is handled and cached separately.
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:
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():
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:
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_NAME variable in the gradle.properties file.
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.
ch.ubique.kmp
network
major.minor.revision