Make Advertisement serializable#1215
Conversation
Adds kotlinx.serialization support to Advertisement and PlatformAdvertisement (e.g. for use as Compose Navigation arguments, see issue #861). Serialization captures a snapshot of the advertisement data (via an internal AdvertisementCapture wire representation). Deserialization restores a PlatformAdvertisement holding the captured data (restored advertisements are not "live"). - Advertisement and PlatformAdvertisement are annotated with @serializable (with AdvertisementSerializer and PlatformAdvertisementSerializer, respectively), so they can be used in serializable classes without additional configuration. - Each platform provides capture/restore implementations: - Android: CapturedAndroidAdvertisement (also Parcelable); bondState and bluetoothDevice are retrieved on-demand from the local adapter. - Apple: CapturedCoreBluetoothAdvertisement; cbPeripheral is retrieved on-demand from the system. - JS/WasmJS: CapturedWebBluetoothAdvertisement; Peripheral cannot be created from a restored advertisement (Web Bluetooth requires a BluetoothDevice), so Peripheral(Advertisement) now fails with a descriptive error for restored advertisements. - JVM: restores as BtleplugAdvertisement (which is already a data holder). Closes #861 Co-authored-by: Travis Wyatt <travis.i.wyatt@gmail.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 0fbc014. Configure here.
| } | ||
|
|
||
| internal actual fun AdvertisementCapture.restore(): PlatformAdvertisement = | ||
| CapturedAndroidAdvertisement(this) |
There was a problem hiding this comment.
Android restored ad Peripheral crash
High Severity
Deserializing an Advertisement on Android yields a CapturedAndroidAdvertisement, but Peripheral(advertisement) still requires a ScanResultAndroidAdvertisement. Navigation or other flows that decode a serialized advertisement and call that builder will fail with a ClassCastException instead of using the restored bluetoothDevice path described for this platform.
Reviewed by Cursor Bugbot for commit 0fbc014. Configure here.
|
This PR exposed some complexities that I don't feel comfortable introducing into Kable. See #861 for more details. |


Closes #861
Adds kotlinx.serialization support to
AdvertisementandPlatformAdvertisement, so they can be used (for example) as navigation arguments with Compose Navigation (per the request in #861 / discussion #788):Design
The platform advertisement implementations wrap live platform objects (
ScanResult,CBPeripheral+ advertisement dictionary,BluetoothAdvertisingEvent), which cannot themselves be serialized. Instead:AdvertisementCapture(the wire representation):name,peripheralName,identifier(asString),isConnectable,rssi,txPower,uuids, full service data and manufacturer data maps, and (Android only) the raw scan recordbytes.PlatformAdvertisementholding the captured data. Restored advertisements are not "live" — properties reflect the advertisement at the time it was serialized.AdvertisementandPlatformAdvertisementare annotated with@Serializable(with = ...)(AdvertisementSerializer/PlatformAdvertisementSerializer), so no additional configuration (contextual serializers, etc.) is needed at usage sites.Serialized advertisements are only guaranteed to deserialize on the same platform they were serialized on (
identifieris a platform specific value that is not portable across platforms).Platform notes
CapturedAndroidAdvertisementParcelable(via customParceler).bondState/bluetoothDeviceare retrieved on-demand from the local adapter, soPeripheral(advertisement)works with restored advertisements.CapturedCoreBluetoothAdvertisementcbPeripheralis retrieved on-demand viaretrievePeripheralsWithIdentifiers(throwsNoSuchElementExceptionif unknown to the system, mirroringPeripheral(Identifier)).CapturedWebBluetoothAdvertisementBluetoothDevice, soPeripheral(advertisement)now fails with a descriptiveIllegalArgumentExceptionfor restored advertisements (directing users toPeripheral(Identifier)).BtleplugAdvertisementString.toIdentifier()).Other changes:
kable-corenow applies theorg.jetbrains.kotlin.plugin.serializationplugin and exposeskotlinx-serialization-coreas anapidependency.BluetoothDevice.toBondState()(previously inlined inScanResultAndroidAdvertisement) for reuse by the restored implementation.Peripheral(Advertisement)on Apple now accepts anyPlatformAdvertisement(previously hard-cast toCBPeripheralCoreBluetoothAdvertisement).Companion.serializer()on the interfaces, plus the two public serializer objects).Testing
commonTest: JSON round-trip of the capture (runs on JVM, Android host, JS, and WasmJS).androidHostTest(Robolectric): JSON round-trip of aScanResult-backed advertisement viaPlatformAdvertisementSerializer(verifies all data retained), stability of re-serialization,AdvertisementSerializer/PlatformAdvertisementSerializerequivalence, andParcelround-trip of a restored advertisement.compileKotlinJvm,compileKotlinJs,compileKotlinWasmJs,assembleAndroidMain,jvmTest,testAndroidHostTest,jsTest/wasmJsTest(only pre-existing environment-dependent Web Bluetooth availability tests fail in headless CI-less Chrome),lintKotlin,apiCheck, andapiDump. Apple targets could not be compiled locally (macOS-only) and rely on CI.