Contributing¶
BlueLib targets an eleven-year Android range, which makes correctness more valuable than cleverness. The rules below exist because each one has already caused a bug somewhere.
Getting set up¶
git clone https://github.com/cybersafetyid/BlueLib.git
cd BlueLib
./gradlew check # tests, lint, domain purity
./gradlew generateCompatibilityMatrix # refresh the generated matrix
mkdocs serve # preview the docs locally
Requirements: JDK 17 and the Android SDK with platforms;android-37.0 — API 37 ships as a minor-versioned
platform, and there is no build-tools;37.0.0 yet, so the build uses build-tools;36.1.0 and lets AGP
download anything else it needs. AGP 9.1.1 or newer is required to build against API 37.
Non-negotiable rules¶
bluelib-corenever importsandroid.*orandroidx.*. A Gradle check enforces it. Logic that has to hold on API 21 belongs in the domain precisely so it can be tested on a plain JVM (Robolectric 4.16 does not run API 21/22).ApiLevelis the only placeBuild.VERSION.SDK_INTis compared. Use its@ChecksSdkIntAtLeastannotated helpers so Android Lint can prove a guarded call, instead of suppressingNewApi.- Minor API levels count. Android 16.1 is API 36.1: use
ApiLevel.isAtLeast(36, 1)when a feature arrived in a QPR release. - No hidden, privileged or reflected APIs. See
ADR 0004. If the platform cannot do it, the answer is a typed
FeatureUnsupportederror with a message that says what to do instead. - Failures are data. Return
BlueLibResult.Failurewith a new or existingBlueLibError, not an exception, unless Kotlin requires a throwable. - Every new public declaration needs a KDoc that explains why, not what. The "what" is visible in the signature.
- Every behaviour change needs a test. Domain logic → JVM test. Platform logic → JVM test if the logic
is pure (see
GattServerState), otherwise an instrumentation test plus a note in the pull request.
Working on the platform layer¶
The platform layer is where the Android release differences live. When adding a branch for a new API level:
- guard it with
ApiLevel.isAtLeast(...); - use the better new API when it is genuinely better, not merely newer (Android 17's
BluetoothGattConnectionSettings, Android 13's value-carrying callbacks, Android 16'sBluetoothSocketSettings); - fail with
FeatureUnsupportedwhen the old API level cannot express the request, rather than ignoring the flag; - verify signatures against the SDK rather than the documentation —
javapon$ANDROID_HOME/platforms/android-37.0/android.jaris authoritative and has already caught two documentation errors (see the research notes).
Pull requests¶
- One topic per pull request; a refactor and a behaviour change in the same change is unreviewable.
- Describe the failure you saw and how you verified the fix. "Works on my Pixel" is not verification; "fails before, passes after, on API 30 and API 36" is.
- Run
./gradlew checkbefore pushing — CI runs the same thing plus a stale-matrix check. - If you touched anything that depends on the platform version, say which API levels you exercised.
Commit messages¶
<scope>: <imperative summary>
Why the change is needed, in one or two sentences. Mention the platform behaviour it works around
and the API levels involved.
Scopes: core, android, facade, testing, build, docs.
Reporting bugs¶
Include:
- BlueLib version (
BlueLib.version). - Device model, Android release and the exact API level (
BlueLib.versionplusPlatformReport.apiLevelDescription). - Peripheral model and firmware version — most GATT problems are peripheral problems.
- The
BluetoothOperationinvolved and the exactBlueLibError(code,context,docsAnchor). - A diagnostics trace:
blueLib.diagnosticscollected around the failure. It contains the platform status codes, which is usually the whole diagnosis.
Issues without a code from the error taxonomy are much harder to act on, because "it does not connect"
has at least eight distinct causes — see Troubleshooting.
Licence¶
Contributions are accepted under the Apache License 2.0. By opening a pull request you agree to license your contribution under the same terms.