Changelog¶
Every release of Reachable, newest first. Versions follow
SemVer; each release is published to Maven Central
and tagged on GitHub. Pin to a tag, never to branch: "main". Each entry is
assembled from the changesets merged since the previous release, when its
release PR is opened.
0.15.0 — 2026-09-29¶
Breaking changes¶
Rename the Swift module to ReachableKit; lower the AAR's compileSdk floor (#16)¶
Swift consumers now import ReachableKit from the reachable-kmp package. Android consumers need compileSdk 34 or later instead of 36.
Swift: the module is now ReachableKit¶
The XCFramework, its Swift module and the SPM product are renamed from
Reachable to ReachableKit, following the <Name>Kit convention that keeps
the module name distinct from the library's public types. The repository is
now happycodelucky/reachable-kmp (the old URL redirects), so SPM's package
identity changes too.
// Before
.package(url: "https://github.com/happycodelucky/reachable.git", from: "0.14.0")
.product(name: "Reachable", package: "reachable")
import Reachable
// After
.package(url: "https://github.com/happycodelucky/reachable-kmp.git", from: "0.15.0")
.product(name: "ReachableKit", package: "reachable-kmp")
import ReachableKit
The Swift API itself is unchanged. Kotlin consumers are unaffected: the Maven
coordinates stay com.happycodelucky.reachable:reachable.
Android: a lower compileSdk floor¶
The AAR used to require consumers to compile against the SDK the library was built with (36). It now declares a deliberate floor of 34, so the library no longer forces a compileSdk bump on your app when ours moves.
JVM bytecode¶
The Android and JVM artifacts now pin Java 21 bytecode explicitly, rather than following the JDK the release happened to be built on.
0.14.0 and earlier¶
Public API¶
Reachability.shared— process-lifetime singleton on theReachabilityinterface'scompanion object. Callable from anywhere at any time, with no Context plumbing required. Replaces the pattern of constructingReachability(context)inApplication.onCreateand threading it through your DI graph.close()onReachability.sharedis an intentional no-op — the singleton lives for the process; the OS reaps the platform observer at process exit.- From Swift:
Reachability.sharedvia an in-framework Swift extension (src/appleMain/swift/Reachability+Shared.swift); the rawReachabilityCompanioncall is hidden.
interface Reachability : AutoCloseableexposingval status: StateFlow<ReachabilityStatus>andfun close().- Single-axis shortcuts on
Reachability:val isReachable: Boolean— synchronous online check.val isDataMetered: Boolean— synchronous metered-path check (cellular, hotspot, or Apple Low Data Mode).val reachable: StateFlow<Boolean>— reactive variant ofisReachable, conflated so transport and metering changes don't emit.val dataMetered: StateFlow<Boolean>— reactive variant ofisDataMetered.
data class ReachabilityStatus(isReachable, transport, isDataMetered). Boolean fields areis-prefixed; the unprefixed names are reserved for theStateFlow<Boolean>shortcuts onReachability. Invariant:!isReachable ⇒ !isDataMetered— unreachable paths never report metered, so callers can readisDataMeteredwithout first checking reachability.enum class Transport { Wifi, Cellular, Ethernet, Other, None }.- No separate
Meteringaxis. Apple'snw_path_is_constrained(Low Data Mode) folds intoisDataMeteredalongsidenw_path_is_expensive; Android's metered/unmetered capability bits map to the same boolean. Consumers who want the cellular-vs-Wi-Fi distinction readtransport; consumers who want "should I defer this transfer" readisDataMetered. The Apple-only Low Data Mode signal is not separately exposed in the public API. - Top-level factories:
Reachability()on Apple,Reachability(context)on Android. Still available for explicit-lifecycle use (tests, per-feature observers). For general use, preferReachability.shared.
Implementation¶
androidx.startupintegration (Android):ReachabilityInitializer(anandroidx.startup.Initializer) is registered in the library'sAndroidManifest.xmlunderInitializationProviderwithtools:node="merge". It attachesReachability.sharedto the applicationContextduring the ContentProvider startup pass — beforeApplication.onCreate. Consumers get auto-init for free; no manifest changes required.androidx.startup:startup-runtime:1.2.0is a newimplementationdependency of the library.- Two-phase Android construction:
AndroidReachabilitynow has a zero-arg primary constructor (no OS calls, no Context) and aninternal fun attach(context)that is idempotent (first-call-wins via CAS). The existingReachability(context)factory still callsattach()immediately. The singleton path defersattach()to theReachabilityInitializer. - Apple:
nw_path_monitorviaplatform.Networkcinterop, on a per-instance serial dispatch queue. - Android:
ConnectivityManager.NetworkCallbackagainstNET_CAPABILITY_INTERNET + NET_CAPABILITY_VALIDATED.status.valueis seeded synchronously fromconnectivityManager.activeNetworkso it's meaningful immediately after construction. - Shared base class owns a
SupervisorJob-rooted scope and an atomic close latch.close()is idempotent.
Distribution¶
- Published to Maven Central as
com.happycodelucky.reachable:reachablevia vanniktech maven-publish. Includes the Android AAR plus thekotlinMultiplatformmetadata and theiosArm64,iosSimulatorArm64,macosArm64klibs, all signed. - Companion artifact
com.happycodelucky.reachable:reachable-testingpublished alongside the main artifact. ProvidesFakeReachabilityandwithFakeReachability { }for installing a scriptable fake asReachability.sharedfor the duration of a test. Add astestImplementation/commonTest— see Installation → Testing support. - A native Swift Package Manager artifact is not published in v0.1. (Added in a later release — see Installation → Swift Package Manager.)
Known limitations¶
- VPN-over-Wi-Fi resolves to
Transport.Wifi(the underlying physical transport). No separateVPNvalue. - No captive-portal detection callback. Apple and Android both handle
captive-portal flow at the OS level; the library surfaces the resolved
isReachableboolean only. - No
androidHostTestforAndroidReachability's deferred-attach path. Blocked on adding Robolectric or aConnectivityManagerwrapper interface. End-to-end coverage currently provided by the Android sample app.