Skip to main content

SDK Callbacks

SDK callbacks are essential for updating your app's UI based on SDK operations. This guide documents all available callbacks, their lifecycle, and how to use them effectively.

Overview​

Callbacks provide real-time notifications about the progress of dock operations. They are invoked during NFC scanning (which reads the dock's metadata), BLE connection, key transmission, and dock state changes. All callbacks are guaranteed to be invoked on the main/UI thread, making it safe to update UI directly.

Callback Lifecycle​

The typical callback sequence for an unlock operation is:

onNFCScanCompleted → onBleConnecting → onBleConnected → onKeySent → 
onDockUnlocked → onDockChainRemoved → [onDockChainInserted] → [onDockSecured] →
onBleDisconnected

For lock operations, the sequence includes onDockChainInserted and onDockSecured before disconnection.

Available Callbacks​

MOSADockSDKCallback Interface​

The Android SDK uses the MOSADockSDKCallback interface. Implement this interface to receive callbacks:

interface MOSADockSDKCallback {
fun onNFCScanCompleted(mosaTag: MosaTag)
fun onBleConnecting()
fun onBleConnected()
fun onBleDisConnected()
fun onKeySent()
fun onDockUnlocked()
fun onDockChainRemoved()
fun onDockChainInserted()
fun onDockSecured()
fun onError(error: MosaError)
}

Callback Reference​

onNFCScanCompleted​

When: NFC scan successfully reads the dock's metadata from the dock

Parameters:

  • Android: mosaTag: MosaTag - Contains dock metadata including dock MAC address and tag data
  • iOS: tag: MosaTag - Contains dock metadata including dock MAC address and tag data
  • Flutter: DockCallbackEvent with type: nfcScanCompleted and data containing dock metadata

Use Case: Update UI to show "Dock found" or extract MAC address for display. The NFC scan reads the dock's metadata and returns it as a MosaTag object in this callback.

Example:

override fun onNFCScanCompleted(mosaTag: MosaTag) {
runOnUiThread {
statusText.text = "Dock found: ${mosaTag.macAddress}"
// MAC address is now available for BLE connection
}
}

onBleConnecting​

When: BLE connection attempt has started

Parameters: None

Use Case: Update UI to show "Connecting to dock..."

Example:

override fun onBleConnecting() {
runOnUiThread {
statusText.text = "Connecting to dock..."
progressBar.visibility = View.VISIBLE
}
}

onBleConnected​

When: BLE connection is successfully established

Parameters: None

Use Case: Update UI to show "Connected" and enable dock operations

Example:

override fun onBleConnected() {
runOnUiThread {
statusText.text = "Connected to dock"
connectionIndicator.setColorFilter(Color.GREEN)
}
}

onBleDisConnected​

When: BLE connection is lost or closed

Parameters: None

Use Case: Update UI to show disconnected state, hide connection indicators

Example:

override fun onBleDisConnected() {
runOnUiThread {
statusText.text = "Disconnected from dock"
connectionIndicator.setColorFilter(Color.GRAY)
}
}

onKeySent​

When: Unlock key has been successfully transmitted to the dock via BLE

Parameters: None

Use Case: Update UI to show "Key sent, waiting for dock response..."

Example:

override fun onKeySent() {
runOnUiThread {
statusText.text = "Key sent, unlocking dock..."
}
}

onDockUnlocked​

When: Dock has successfully unlocked

Parameters: None

Use Case: Update UI to show "Dock unlocked" and indicate user can remove bike

Example:

override fun onDockUnlocked() {
runOnUiThread {
statusText.text = "Dock unlocked!"
unlockIndicator.setColorFilter(Color.GREEN)
// User can now remove bike
}
}

onDockChainRemoved​

When: Chain has been removed from the dock (bike can be taken)

Parameters: None

Use Case: Update UI to show "Chain removed, bike ready" or start rental timer

Example:

override fun onDockChainRemoved() {
runOnUiThread {
statusText.text = "Chain removed - bike ready!"
// For bike sharing: start rental timer
// For bike parking: bike is now parked
}
}

onDockChainInserted​

When: Chain has been inserted back into the dock (bike is locked)

Parameters: None

Use Case: Update UI to show "Chain inserted, securing dock..." or stop rental timer

Example:

override fun onDockChainInserted() {
runOnUiThread {
statusText.text = "Chain inserted, securing dock..."
// For bike sharing: stop rental timer
// For bike parking: bike is being secured
}
}

onDockSecured​

When: Dock has been fully secured/locked

Parameters: None

Use Case: Update UI to show "Dock secured" and indicate operation complete

Example:

override fun onDockSecured() {
runOnUiThread {
statusText.text = "Dock secured - operation complete!"
progressBar.visibility = View.GONE
// Operation complete, can navigate away or show success message
}
}

onError​

When: An error occurs during any SDK operation

Parameters:

  • Android: error: MosaError - Structured error with type, message, and metadata
  • iOS: error: MosaError - Structured error with type, message, and metadata
  • Flutter: DockCallbackEvent with type: error and error field containing error information

Use Case: Display error message to user, handle retry logic, log error for debugging

Example:

override fun onError(error: MosaError) {
runOnUiThread {
when (error.errorType) {
MosaErrorType.NFC_DISABLED -> {
showError("Please enable NFC in settings")
// Navigate to NFC settings
}
MosaErrorType.BLE_DISABLED -> {
showError("Please enable Bluetooth in settings")
// Navigate to Bluetooth settings
}
MosaErrorType.DOCK_TIMEOUT -> {
showError("Dock operation timed out. Please try again.")
// Show retry button
}
else -> {
showError("Error: ${error.message}")
}
}
progressBar.visibility = View.GONE
}
}

Complete Implementation Example​

Here's a complete example showing how to implement callbacks for a dock unlock operation:

class MainActivity : AppCompatActivity(), MOSADockSDKCallback {
private lateinit var sdk: MOSADockSDK

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

sdk = MOSADockSDK.builder()
.setActivity(this)
.setApiKey("your-api-key")
.setUserId("user-123")
.build()
}

private fun unlockBike() {
lifecycleScope.launch {
val unlockKey = sdk.requestUnlockKey(
type = MOSADockUnlockKeyType.BikeUnlock,
areaId = "area-123"
)

if (unlockKey != null) {
sdk.unlockSmartLock(
unlockKey = unlockKey,
gfaLocationType = MOSADockLocationType.PARKING,
callback = this@MainActivity
)
}
}
}

override fun onNFCScanCompleted(mosaTag: MosaTag) {
runOnUiThread {
statusText.text = "Dock found: ${mosaTag.macAddress}"
}
}

override fun onBleConnecting() {
runOnUiThread {
statusText.text = "Connecting..."
progressBar.visibility = View.VISIBLE
}
}

override fun onBleConnected() {
runOnUiThread {
statusText.text = "Connected"
}
}

override fun onKeySent() {
runOnUiThread {
statusText.text = "Key sent, unlocking..."
}
}

override fun onDockUnlocked() {
runOnUiThread {
statusText.text = "Dock unlocked!"
}
}

override fun onDockChainRemoved() {
runOnUiThread {
statusText.text = "Chain removed - bike ready!"
progressBar.visibility = View.GONE
}
}

override fun onBleDisConnected() {
runOnUiThread {
statusText.text = "Disconnected"
}
}

override fun onError(error: MosaError) {
runOnUiThread {
statusText.text = "Error: ${error.message}"
progressBar.visibility = View.GONE
showErrorDialog(error)
}
}

// Implement other callback methods...
override fun onDockChainInserted() {}
override fun onDockSecured() {}
}

Callback Sequence Diagrams​

Unlock Operation (Bike Parking)​

Lock Operation (Bike Parking)​

Best Practices​

1. Subscribe to Callbacks Early​

Flutter: Subscribe to event streams before initializing the SDK:

// ✅ Correct: Subscribe first
sdk.dockCallbackEvents.listen((event) { ... });
await sdk.initialize(...);

// ❌ Wrong: Initialize first
await sdk.initialize(...);
sdk.dockCallbackEvents.listen((event) { ... }); // May miss early events

2. Update UI on Main Thread​

All callbacks are already invoked on the main thread, but ensure UI updates happen synchronously:

override fun onDockUnlocked() {
// Already on main thread, but use runOnUiThread for safety
runOnUiThread {
statusText.text = "Unlocked!"
}
}

3. Handle Errors Gracefully​

Always implement onError to handle errors and provide user feedback:

override fun onError(error: MosaError) {
when (error.errorType) {
MosaErrorType.NFC_DISABLED -> showEnableNfcDialog()
MosaErrorType.BLE_DISABLED -> showEnableBleDialog()
MosaErrorType.DOCK_TIMEOUT -> showRetryDialog()
else -> showGenericError(error.message)
}
}

4. Clean Up Subscriptions​

Flutter: Cancel subscriptions when done:

@override
void dispose() {
_callbackSubscription?.cancel();
super.dispose();
}

Error Handling​

Errors are reported via the onError callback with structured error information:

  • Error Type: Categorized error type (NFC, BLE, Dock, SDK, etc.)
  • Message: Human-readable error message
  • Metadata: Additional context (sessionId, deviceId, etc.)

Next Steps​


For complete API reference, see the API Reference.