HTTP and API networking library for Android, built directly on Cronet.
Upgrading from 2.x is a breaking change. CHANGELOG.md lists every removed and renamed symbol.
repositories {
maven { url 'https://git.androidacy.com/api/packages/Androidacy/maven' }
}
dependencies {
implementation 'com.github.Androidacy:android-apifier:3.0.2'
}Releases up to 3.0.0 are also on JitPack. Anything newer is published only to the registry above.
val client = ApifierClient(context)
viewModelScope.launch {
client.get("https://api.example.com/data").use { response ->
if (response.isSuccessful) render(response.body.string())
}
}
client.close()send, get, post, delete, head, download and upload are suspend functions on
Requester. Constructing the client blocks — provider selection can reach Google Play services and
wait on a Dynamite download — so build it off the main thread. close() releases the engine,
thread pools and network callback.
suspendcall surface; theCall/CallbackAPI still works but is deprecated- Cronet transport with QUIC, HTTP/2 and Brotli, over a provider ladder:
HttpEngine, GMS, app-packaged, fallback, reflective Java provider - Typed failures —
ApifierExceptionsubtypes rather than a bareIOException - Per-attempt request events: outcome, timing, byte counts
- DNS resolver qualification and per-host IP pinning
- Public-suffix-scoped cookie jar, AES-GCM encrypted under an Android Keystore key
- Streaming multipart and file uploads
- Exponential backoff with per-host circuit breakers
- Kotlin DSL for
NetworkConfig
val client = ApifierClient(context) {
cronet {
enableQuic = true
enableHttp2 = true
quicHint("api.example.com")
cacheDirectory = context.cacheDir.resolve("cronet")
}
timeouts { read = 60.seconds }
retry { retryOn5xx = true }
maxAttempts(3)
timeout(90.seconds)
ensureTrustworthyResolver(true)
hostIpPins { pin("api.example.com", "203.0.113.10") }
cookieStorage(MyCookieStorage())
header("User-Agent", "MyApp/1.0")
dynamicHeader("Authorization") { getAuthToken() }
}maxAttempts and timeout are also per-call modifiers (client.maxAttempts(1).get(url)); a call
that sets neither uses the client's values.
read and call are the only timeouts. Retries are off until you raise maxAttempts; circuit
breakers are on by default.
ApifierClient.events is a SharedFlow<RequestEvent> with one event per attempt, carrying
outcome, error code, response code, elapsed and time-to-first-byte, bytes sent and received, host,
method, attempt number, serving provider, and whether a retry follows.
scope.launch {
client.events.collect { event -> log("${event.method} ${event.host} -> ${event.outcome}") }
}
client.maxAttempts(1).observe { report(it) }.get("https://api.example.com/data")observe() sees only the event that ended that call.
Each client qualifies the platform resolver once per network: names that must not resolve, and public names that must resolve to public address space. Each call's host is then checked on its own, requiring every address it resolves to be public.
val trustworthy = client.isResolverTrustworthy()ensureTrustworthyResolver(true) refuses calls while qualification fails. Left off, the checks
still run but block nothing.
Enforcement refuses any host on private address space — LAN devices, VPN-reachable staging,
.local names and RFC 1918 APIs all fail while it is on.
For a host with known addresses, pin instead:
hostIpPins { pin("api.example.com", "203.0.113.10") }Any pin turns on enforcement for the whole client. The declared set is the whole set: a pinned host
is refused with ApifierException.DnsUntrusted unless every resolved address is declared. Pins
suit neither geo-DNS fronted hosts nor address migrations, and are checked against this library's
resolution — Cronet resolves again to connect, so a pin narrows the window rather than closing it.
send() throws ApifierException on failure, IllegalStateException if the client is closed, and
CancellationException if the calling coroutine is cancelled. Every ApifierException reports an
ErrorCode and whether it is retryable.
| Subtype | Raised when |
|---|---|
Transport |
Cronet network failure |
CircuitOpen |
Host breaker is open |
Cancelled |
Call was cancelled |
CallTimeout |
Call exceeded its timeout |
RedirectRefused |
Redirect was not followed |
DnsUntrusted |
Resolver or pin check failed |
HttpError |
Non-2xx passed through successOrThrow() |
Unexpected |
Anything unmodelled; carries the original as its cause |
Implement CookieStorage:
class MyCookieStorage : CookieStorage {
override fun getStringSet(key: String, defaultValue: Set<String>?) =
encryptedPrefs.getStringSet(key, defaultValue)
override fun putStringSet(key: String, value: Set<String>) =
encryptedPrefs.edit { putStringSet(key, value) }
override fun remove(key: String) =
encryptedPrefs.edit { remove(key) }
}Cookies are encrypted with a hardware-backed AES-GCM key, using StrongBox or the TEE where the device has one. A device with no usable Keystore drops cookies rather than storing them in cleartext.
val progress = MutableSharedFlow<Progress>(extraBufferCapacity = 64)
scope.launch {
progress.collect { (bytesTransferred, contentLength) ->
if (contentLength > 0) updateProgressBar((bytesTransferred * 100 / contentLength).toInt())
}
}
scope.launch {
client.progress(progress).download(url).close()
client.progress(progress).upload(url, files, fileNames).close()
}Build the sink with extraBufferCapacity > 0. A default MutableSharedFlow<Progress>() reports
nothing at all.
Upload and download report into the same sink, upload first. Upload progress requires a file-backed
body — asRequestBody(File) or a multipart part built from one; String and ByteArray bodies
report nothing. A body of unknown length never reaches its total, and a retried upload restarts
from zero.
TLS trust runs through Cronet, which honors your app's Network Security Configuration. Configure trust anchors, certificate pinning and cleartext policy there.
<network-security-config>
<domain-config>
<domain includeSubdomains="true">api.example.com</domain>
<pin-set>
<pin digest="SHA-256">base64EncodedPin==</pin>
</pin-set>
</domain-config>
</network-security-config>- Android 8.0 (API 26)
- Java 21
- Kotlin 2.4
API documentation ships as the javadoc artifact alongside each release.
Apache 2.0. See LICENSE.