Skip to content

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.

// app/build.gradle.kts
dependencies {
    implementation("com.happycodelucky.reachable:reachable:0.15.0")
}
// desktop-app/build.gradle.kts
dependencies {
    implementation("com.happycodelucky.reachable:reachable:0.15.0")
}
// shared/build.gradle.kts
kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation("com.happycodelucky.reachable:reachable:0.15.0")
        }
    }
}

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:

// app/build.gradle.kts
dependencies {
    implementation("com.happycodelucky.reachable:reachable:0.15.0")
    testImplementation("com.happycodelucky.reachable:reachable-testing:0.15.0")
}
// shared/build.gradle.kts
kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation("com.happycodelucky.reachable:reachable:0.15.0")
        }
        commonTest.dependencies {
            implementation("com.happycodelucky.reachable:reachable-testing:0.15.0")
        }
    }
}

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.

  1. File → Add Package Dependencies…
  2. Enter https://github.com/happycodelucky/reachable-kmp.git.
  3. Keep Up to Next Major Version with the suggested version.
  4. Add the ReachableKit product to your app target.
dependencies: [
    .package(url: "https://github.com/happycodelucky/reachable-kmp.git", from: "0.15.0"),
],
targets: [
    .target(
        name: "MyApp",
        dependencies: [
            .product(name: "ReachableKit", package: "reachable-kmp"),
        ]
    ),
]

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:

mise run publish:local

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:

repositories {
    mavenLocal()
    mavenCentral()
}

…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.

Verification

// Singleton path — no setup required.
val r = Reachability.shared
println(r.status.value)   // ReachabilityStatus(isReachable=…, transport=…, isDataMetered=…)
// r.close() is a no-op on .shared; omit it.
// Singleton path — no setup required.
let r = Reachability.shared
print(r.status.value!)    // ReachabilityStatus(isReachable: …, transport: …, isDataMetered: …)
// r.close() is a no-op on .shared; omit it.