Best Practices
Patterns for building reliable apps with the Igloohome BLE SDK.
Architecture
The SDK orchestrates BLE and server operations together. For example, createPin() writes the PIN to lock hardware via BLE, registers it on the server, and rolls back if the server call fails — all in one call.
Singleton Pattern
Only one IglooPlugin instance should exist at a time. Multiple instances hold separate connection state and will cause undefined BLE behavior.
Token Management
The Igloohome SDK uses separate tokens for different operations:
Sync Tokens
The sync() and syncWithStatus() methods require three dedicated tokens:
| Token | Purpose |
|---|---|
getDeviceToken |
Reading device state from the server. |
storeLogsToken |
Uploading activity logs to the server. |
updateDeviceToken |
Patching device info (battery, firmware version). |
Access Token
All other methods (createPin, addKeycard, addFingerprint, link, checkFirmwareUpdate, performDfu, etc.) accept a single accessToken: String parameter. This token is always required.
Connection Management
- One operation at a time. BLE operations are serialized internally per device — don't fire multiple lock/unlock calls concurrently on the same device.
- Disconnect when done. Call
disconnect(deviceId)after your operation completes to free BLE resources. - The SDK connects automatically. There is no separate
connect()method —lock(),sync(),pair()etc. all connect internally.
Coroutine Patterns
All BLE operations are suspend functions or return Flow. Use structured concurrency.
Suspend Functions
Flow Collection
Flow with Lifecycle
Swift Concurrency Patterns
All BLE operations are async throws functions or return AsyncStream/AsyncThrowingStream. Use structured concurrency (Task, async let, task cancellation) in place of Kotlin's coroutine scopes.
Async Functions
Stream Collection
Stream with View Lifecycle
Error Handling
Catch at the UI layer, not deep in the call stack.
Retry only transient errors: TimeoutException, ConnectionException, and ApiException with 5xx status (Android); IgloohomeError.timeout, .connection, and IgloohomeError.api with 5xx status (iOS). Terminal errors like DevicePairedException/IgloohomeError.devicePaired or LockStorageFullException/IgloohomeError.lockStorageFull require user action.
Sync with Granular Error Handling
Use syncWithStatus() to handle errors per sync operation instead of failing the entire sync on the first error.
Each operation emits a SyncResultStatus with its own success/failure state, so a failure in one step (e.g. log upload) does not prevent the others from completing. On iOS, prefer syncWithStatus over the deprecated sync() for this reason too.
Recommended Operation Sequences
First-Time Device Setup
scanDevice()(Android) /scansLock()(iOS) — find the lockpair()— register on accountcalibrate()— tune motor directionsync()(Android) /syncWithStatus()(iOS,sync()is deprecated) — set clock and read batterysetWifiConfig()— if bridge, configure WiFi
Regular Operations
lock()/unlock()— control the locksync()(Android) /syncWithStatus()(iOS) — periodically sync time and logs
Access Management
createPin()/addKeycard()/addFingerprint()— add accessdeletePin()/deleteKeycard()/deleteFingerprint()— remove access
Firmware Update
checkFirmwareUpdate()— check for updatesperformDfu()— apply update (keep screen on)sync()— verify device state after update
Device Removal
unlink()— remove all accessories firstunpair()— remove the lock
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
BluetoothException on every call |
Bluetooth disabled or permissions not granted | Check BluetoothAdapter.isEnabled and request runtime permissions |
ConnectionException frequently |
Device out of BLE range | Move within 2–3 meters of the lock |
TimeoutException on first call |
Lock in deep sleep | Retry — first connection wakes the lock |
DevicePairedException during pair |
Lock already registered | Check server if you own it; factory reset if transferring |
HasLinkedDeviceException during unpair |
Accessories still linked | Call unlink() for each accessory first |
DuplicatePinException |
Same PIN exists on lock | Choose a different PIN code |
LockStorageFullException |
Lock PIN/card slots exhausted | Delete existing access before adding new ones |
BatteryLowException during DFU |
Battery below threshold | Charge or replace batteries before DFU |
Multiple IglooPlugin instances |
Creating SDK in Activity/Fragment | Use application-scoped singleton |
| BLE operations fail silently | RxJava undeliverable exceptions | SDK handles these internally — upgrade if on old version |
The same table for iOS:
| Problem | Cause | Solution |
|---|---|---|
IgloohomeError.bluetoothIsTurnedOff on every call |
Bluetooth disabled, or NSBluetoothAlwaysUsageDescription missing from Info.plist |
Enable Bluetooth; verify the Info.plist keys are present (iOS kills the app on first scan without them) |
IgloohomeError.lockNotFound / .connection frequently |
Device out of BLE range | Move within 2–3 meters of the lock |
IgloohomeError.timeout on first call |
Lock in deep sleep | Retry — first connection wakes the lock |
IgloohomeError.devicePaired during pair |
Lock already registered | Check server if you own it; factory reset if transferring |
IgloohomeError.hasLinkedDevice during unpair |
Accessories still linked | Call unlink() for each accessory first |
IgloohomeError.duplicatePin |
Same PIN exists on lock | Choose a different PIN code |
IgloohomeError.lockStorageFull |
Lock PIN/card slots exhausted | Delete existing access before adding new ones |
IgloohomeError.batteryLow during DFU |
Battery below threshold | Charge or replace batteries before DFU |
| Testing on Simulator does nothing | Bluetooth is unavailable in the iOS Simulator | Test on a physical device |
Two CreateKeycardAccessResponse / CalibrationLockingDirection types found |
Both IgloohomeSDK and IglooSDKCore declare a type of that name (different raw values for CalibrationLockingDirection) |
Import only IgloohomeSDK for app code where possible; be explicit about which module's type you mean if you also import IglooSDKCore |