Installation¶
Reachable ships through two channels:
| Channel | For | Artifacts |
|---|---|---|
| Maven Central | Gradle — Android, JVM, Kotlin Multiplatform | Android AAR, JVM jar, kotlinMultiplatform metadata, per-target klibs (iosArm64, iosSimulatorArm64, macosArm64) |
| Swift Package Manager | Pure-Swift iOS / macOS apps, no Kotlin toolchain | Prebuilt ReachableKit.xcframework, hosted as a GitHub Release asset |
Kotlin Multiplatform projects should use the Maven artifact from
commonMain — KMP resolves the right per-target slice automatically, and
the Swift surface is produced at your project's framework build. The
Swift package is for apps with no Kotlin in them at all — see
Swift Package Manager below.
Platform floors¶
| Platform | Floor |
|---|---|
| iOS / iPadOS | iOS 18 |
| macOS | macOS 15 |
| Android | API 30 (Android 11), arm64-v8a only |
| JVM | 21 (desktop / server, any OS) |
| Kotlin | 2.3.x (K2) |
Gradle (Android, JVM, KMP)¶
Maven Central is on the default repository list, so no repositories { }
block changes are needed.
android.permission.ACCESS_NETWORK_STATE is declared in the library's own
manifest and merged in at build time. It's a normal-protection permission,
so no runtime grant is needed. The JVM target needs no permissions —
reachability is read from the local interface table, with no network
traffic.
Testing support¶
A companion artifact — com.happycodelucky.reachable:reachable-testing —
ships FakeReachability and the withFakeReachability { } helper for
installing it as Reachability.shared for the duration of a test. Add it
as a test dependency:
The testing artifact does not ship as an XCFramework or a Swift package —
only the main reachable artifact has an SPM distribution. FakeReachability
is consumed via KMP klibs from Maven Central.
Basic usage¶
@Test
fun deviceIsOnline() = runTest {
withFakeReachability(
initial = ReachabilityStatus(isReachable = true, transport = Transport.Wifi, isDataMetered = false),
) { fake ->
val vm = MyViewModel() // reads Reachability.shared
assertTrue(vm.online)
fake.setReachable(false)
assertFalse(vm.online)
}
}
withFakeReachability installs the fake as Reachability.shared,
runs the block, then uninstalls and closes the fake in finally — even
when the block throws.
Swift Package Manager¶
Pure-Swift apps consume Reachable as a binary Swift package: a prebuilt
ReachableKit.xcframework with iosArm64, iosSimulatorArm64,
and macosArm64 slices. No Kotlin toolchain, no Gradle, no authentication —
each release tag carries the package manifest, and the binary is a public
GitHub Release asset, pinned by sha256 checksum in that manifest.
- File → Add Package Dependencies…
- Enter
https://github.com/happycodelucky/reachable-kmp.git. - Keep Up to Next Major Version with the suggested version.
- Add the ReachableKit product to your app target.
Then import ReachableKit. The Swift bridge is baked into the framework, so
StateFlow arrives as a Swift AsyncSequence, sealed types switch
exhaustively via onEnum(of:), and suspend functions are async throws.
Each release tag carries a Package.swift whose binary target references
that release's ReachableKit.xcframework.zip asset, so swift package
resolve downloads a prebuilt framework instead of compiling Kotlin.
If you're working from a KMP project, don't add the Swift package — the
iOS / macOS targets are consumed transparently via the
kotlinMultiplatform metadata published alongside the Android AAR, and
the Swift surface is produced at your project's framework build time, not
the library's.
Local development override¶
When working on the library itself, publish to your local Maven repository and consume from there:
It publishes the version under development as a -SNAPSHOT (the next
release the pending changes add up to), never a released version — so it
can't shadow the real artifact from Maven Central.
A consuming Gradle project then adds mavenLocal() to its repository list:
…and pins the dependency to the snapshot version the task printed (e.g.
com.happycodelucky.reachable:reachable:0.15.0-SNAPSHOT). Re-running
mise run publish:local overwrites the cached artifact; the consumer picks
up the change on the next Gradle sync.
See .github/PUBLISHING.md for the full local + Maven Central pipeline.