Getting Started
This guide will help you integrate the MOSA SDK into your mobile application. Follow the steps below to get started.
Prerequisites
Device Requirements
- Android: Device with NFC and BLE support, running Android 7.0 (API Level 24) or higher
- iOS: iPhone 7 or later with NFC and BLE support, running iOS 13.0 or higher
- Flutter: Same requirements as the native platform
Development Tools
- Android: Android Studio with Android SDK
- iOS: Xcode 12.0 or later
- Flutter: Flutter SDK (if using Flutter)
API Keys
You'll need API keys from the MOSA admin dashboard. See API Key Management for instructions on obtaining them.
Installation
- Android
- iOS
- Flutter
Add Dependency
Add the SDK dependency to your build.gradle.kts:
dependencies {
implementation("com.mosadock:sdk:1.0.0") // Replace with actual version
}
Or in build.gradle:
dependencies {
implementation 'com.mosadock:sdk:1.0.0'
}
Configure Permissions
Add required permissions to your AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Bluetooth permissions -->
<!-- Android 12+ (API 31+) -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<!-- Legacy Bluetooth (Android 11 and below) -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<!-- Location permissions (required for BLE scanning) -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- NFC permissions -->
<uses-permission android:name="android.permission.NFC" />
<!-- Foreground service permissions -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- Android 13+ -->
<!-- Network permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Hardware features -->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
<uses-feature android:name="android.hardware.nfc" android:required="true" />
</manifest>
Add Dependency
Swift Package Manager (Recommended)
- In Xcode, go to File → Add Packages...
- Enter the package URL (contact MOSA for the repository URL)
- Select the version or branch
- Add to your target
CocoaPods
Add to your Podfile:
pod 'MosaDockSdk', '~> 1.0.0'
Then run:
pod install
Configure Permissions
Add required usage descriptions to your Info.plist:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to connect to and unlock MOSA docks.</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>This app uses Bluetooth to connect to and unlock MOSA docks.</string>
<key>NFCReaderUsageDescription</key>
<string>This app uses NFC to scan MOSA dock tags for unlocking and parking operations.</string>
Note: NSBluetoothPeripheralUsageDescription is legacy but still recommended for compatibility.
Enable Capabilities (Optional)
If you need BLE operations in the background, enable the following capability in your app target:
- Background Modes → Uses Bluetooth LE accessories
Add Dependency
Add the plugin to your pubspec.yaml:
dependencies:
mosa_dock_sdk_flutter: ^1.0.0 # Replace with actual version
Then run:
flutter pub get
Configure Permissions
Android
Add required permissions to android/app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Bluetooth permissions -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<!-- Location permissions -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- NFC permissions -->
<uses-permission android:name="android.permission.NFC" />
<!-- Foreground service permissions -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- Network permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Hardware features -->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
<uses-feature android:name="android.hardware.nfc" android:required="true" />
</manifest>
iOS
Add required usage descriptions to ios/Runner/Info.plist:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to connect to and unlock MOSA docks.</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>This app uses Bluetooth to connect to and unlock MOSA docks.</string>
<key>NFCReaderUsageDescription</key>
<string>This app uses NFC to scan MOSA dock tags for unlocking and parking operations.</string>
Initialize SDK
After installation, initialize the SDK in your application. The SDK requires certain fields to be configured during initialization.
Required Fields
apiKey(String, required) - Your API key obtained from the MOSA admin dashboard. Used to authenticate API requests. See API Key Management for instructions on obtaining API keys.userId(String, required) - Unique identifier for the current user. This can be a unique ID from your app (e.g., user account ID, or any identifier that uniquely identifies the user). This is used for all dock operations and session management.
Platform-Specific Initialization
- Android
- iOS
- Flutter
import to.mosa.lib.dock.MOSADockSDK
// Initialize SDK
val sdk = MOSADockSDK.builder()
.setActivity(this) // Activity context
.setApiKey("your-api-key") // Required: Your API key
.setUserId("user-123") // Required: User identifier
.build()
Required Parameters:
setActivity(activity)- Activity context for SDK operationssetApiKey(apiKey)- Your API key from MOSA admin dashboardsetUserId(userId)- Unique user identifier
import MosaDockSdk
// Initialize SDK
MosaDockSDK.initialize(
with: self, // UIViewController context
apiKey: "your-api-key", // Required: Your API key
userId: "user-123" // Required: User identifier
)
let sdk = MosaDockSDK.shared
Required Parameters:
with: UIViewController- View controller context for SDK operationsapiKey: String- Your API key from MOSA admin dashboarduserId: String- Unique user identifier
import 'package:mosa_dock_sdk_flutter/mosa_dock_sdk_flutter.dart';
final mosaDockSdk = MosaDockSdk();
// IMPORTANT: Subscribe to callbacks BEFORE initializing SDK
mosaDockSdk.dockCallbackEvents.listen((event) {
// Handle callbacks
});
// Initialize SDK
await mosaDockSdk.initialize(
apiKey: 'your-api-key', // Required: Your API key
userId: 'user-123', // Required: User identifier
);
Required Parameters:
apiKey: String- Your API key from MOSA admin dashboarduserId: String- Unique user identifier
Note: Always subscribe to event streams before calling initialize() to ensure you receive all callbacks.
Request Unlock Key
The SDK uses unlock keys to authenticate and authorize dock operations. Keys are requested from the backend and expire after 15 minutes.
How It Works
- Session Creation: A session is automatically created (or reused) when requesting certain key types
- Key Request: Request an unlock key from the backend with the appropriate type and parameters
- Key Usage: Use the key with
unlockSmartLock()to perform dock operations
Available Unlock Key Types
| Key Type | Use Case | Required Arguments | Location Type |
|---|---|---|---|
bikeLock | Lock a bike at a parking area | areaId | PARKING |
bikeUnlock | Unlock a bike from a parking dock | None | PARKING |
rentBikeUnlock | Rent/unlock a bike from a sharing area | areaId | SHARING |
rentBikeLock | Return/lock a bike at a sharing area | areaId | SHARING |
testLock | Perform a test unlock. The dock won't go into the in-use state and remains ready for use again after attaching the chain back. | areaId | PARKING or SHARING |
For complete details about unlock keys, including properties, methods, and validation rules, see the API Reference.
Request Unlock Key Example
- Android
- iOS
- Flutter
lifecycleScope.launch {
val unlockKey = sdk.requestUnlockKey(
type = MOSADockUnlockKeyType.BikeLock,
areaId = "area-123"
)
if (unlockKey != null) {
// Use key for dock operation
performDockOperation(unlockKey)
} else {
// Handle key request failure
showError("Failed to request unlock key")
}
}
Task {
do {
let unlockKey = try await sdk.requestUnlockKey(
type: .bikeLock,
areaId: "area-123",
userId: "user-123"
)
// Use key for dock operation
await performDockOperation(unlockKey)
} catch {
// Handle error
showError("Failed to request unlock key: \(error)")
}
}
try {
final unlockKey = await mosaDockSdk.requestUnlockKey(
type: MosaDockUnlockKeyType.bikeLock,
areaId: 'area-123',
userId: 'user-123',
);
// Use key for dock operation
await performDockOperation(unlockKey);
} catch (e) {
// Handle error
showError('Failed to request unlock key: $e');
}
Key Request Flow
Bike Parking
Bike parking allows users to lock and unlock their bikes at any available dock in a parking area using an unlock key.
Lock Bike
Lock a bike at a parking dock:
- Android
- iOS
- Flutter
// Step 1: Request lock key
lifecycleScope.launch {
val unlockKey = sdk.requestUnlockKey(
type = MOSADockUnlockKeyType.BikeLock,
areaId = "area-123"
)
if (unlockKey != null) {
// Step 2: Execute lock operation
sdk.unlockSmartLock(
unlockKey = unlockKey,
gfaLocationType = MOSADockLocationType.PARKING,
callback = object : MOSADockSDKCallback {
override fun onNFCScanCompleted(mosaTag: MosaTag) {
// NFC scan completed
}
override fun onBleConnecting() {
// BLE connecting
}
override fun onBleConnected() {
// BLE connected
}
override fun onKeySent() {
// Key sent to dock
}
override fun onDockUnlocked() {
// Dock unlocked
}
override fun onDockChainInserted() {
// Chain inserted - bike locked
}
override fun onDockSecured() {
// Dock secured - operation complete
}
override fun onError(error: MosaError) {
// Handle error
}
// Implement other callbacks...
}
)
}
}
Task {
do {
// Step 1: Request lock key
let unlockKey = try await sdk.requestUnlockKey(
type: .bikeLock,
areaId: "area-123",
userId: "user-123"
)
// Step 2: Execute lock operation
try await sdk.unlockSmartLock(
with: unlockKey,
location: .parking,
delegate: self
)
} catch {
// Handle error
showError("Lock operation failed: \(error)")
}
}
// Delegate callbacks
func onNFCScanCompleted(_ tag: MosaTag) {
// NFC scan completed
}
func onBleConnecting() {
// BLE connecting
}
func onBleConnected() {
// BLE connected
}
func onKeySent() {
// Key sent to dock
}
func onDockUnlocked() {
// Dock unlocked
}
func onDockChainInserted() {
// Chain inserted - bike locked
}
func onDockSecured() {
// Dock secured - operation complete
}
func onError(_ error: MosaError) {
// Handle error
}
try {
// Step 1: Request lock key
final unlockKey = await mosaDockSdk.requestUnlockKey(
type: MosaDockUnlockKeyType.bikeLock,
areaId: 'area-123',
userId: 'user-123',
);
// Step 2: Execute lock operation
await mosaDockSdk.handleClientUnlock(
unlockKey: unlockKey,
locationType: MosaDockLocationType.parking.value,
);
} catch (e) {
// Handle error
showError('Lock operation failed: $e');
}
// Callback handling (set up before calling handleClientUnlock)
mosaDockSdk.dockCallbackEvents.listen((event) {
switch (event.type) {
case DockCallbackType.nfcScanCompleted:
// NFC scan completed
break;
case DockCallbackType.bleConnecting:
// BLE connecting
break;
case DockCallbackType.bleConnected:
// BLE connected
break;
case DockCallbackType.keySent:
// Key sent to dock
break;
case DockCallbackType.dockUnlocked:
// Dock unlocked
break;
case DockCallbackType.dockChainInserted:
// Chain inserted - bike locked
break;
case DockCallbackType.dockSecured:
// Dock secured - operation complete
break;
case DockCallbackType.error:
// Handle error
break;
default:
break;
}
});
Callback Sequence: onNFCScanCompleted → onBleConnecting → onBleConnected → onKeySent → onDockUnlocked → onDockChainInserted → onDockSecured
Unlock Bike
Unlock the previously parked bike from the exact dock that was used before. This operation won't work on any other docks:
- Android
- iOS
- Flutter
// Step 1: Request unlock key
lifecycleScope.launch {
val unlockKey = sdk.requestUnlockKey(
type = MOSADockUnlockKeyType.BikeUnlock,
areaId = "area-123" // areaId from session
)
if (unlockKey != null) {
// Step 2: Execute unlock operation
sdk.unlockSmartLock(
unlockKey = unlockKey,
gfaLocationType = MOSADockLocationType.PARKING,
callback = object : MOSADockSDKCallback {
override fun onNFCScanCompleted(mosaTag: MosaTag) {
// NFC scan completed
}
override fun onBleConnecting() {
// BLE connecting
}
override fun onBleConnected() {
// BLE connected
}
override fun onKeySent() {
// Key sent to dock
}
override fun onDockUnlocked() {
// Dock unlocked
}
override fun onDockChainRemoved() {
// Chain removed - bike ready to take
}
override fun onError(error: MosaError) {
// Handle error
}
// Implement other callbacks...
}
)
}
}
Task {
do {
// Step 1: Request unlock key
let unlockKey = try await sdk.requestUnlockKey(
type: .bikeUnlock,
areaId: "area-123", // areaId from session
userId: "user-123"
)
// Step 2: Execute unlock operation
try await sdk.unlockSmartLock(
with: unlockKey,
location: .parking,
delegate: self
)
} catch {
// Handle error
showError("Unlock operation failed: \(error)")
}
}
// Delegate callbacks
func onNFCScanCompleted(_ tag: MosaTag) {
// NFC scan completed
}
func onBleConnecting() {
// BLE connecting
}
func onBleConnected() {
// BLE connected
}
func onKeySent() {
// Key sent to dock
}
func onDockUnlocked() {
// Dock unlocked
}
func onDockChainRemoved() {
// Chain removed - bike ready to take
}
func onError(_ error: MosaError) {
// Handle error
}
try {
// Step 1: Request unlock key
final unlockKey = await mosaDockSdk.requestUnlockKey(
type: MosaDockUnlockKeyType.bikeUnlock,
areaId: 'area-123', // areaId from session
userId: 'user-123',
);
// Step 2: Execute unlock operation
await mosaDockSdk.handleClientUnlock(
unlockKey: unlockKey,
locationType: MosaDockLocationType.parking.value,
);
} catch (e) {
// Handle error
showError('Unlock operation failed: $e');
}
// Callback handling
mosaDockSdk.dockCallbackEvents.listen((event) {
switch (event.type) {
case DockCallbackType.nfcScanCompleted:
// NFC scan completed
break;
case DockCallbackType.bleConnecting:
// BLE connecting
break;
case DockCallbackType.bleConnected:
// BLE connected
break;
case DockCallbackType.keySent:
// Key sent to dock
break;
case DockCallbackType.dockUnlocked:
// Dock unlocked
break;
case DockCallbackType.dockChainRemoved:
// Chain removed - bike ready to take
break;
case DockCallbackType.error:
// Handle error
break;
default:
break;
}
});
Callback Sequence: onNFCScanCompleted → onBleConnecting → onBleConnected → onKeySent → onDockUnlocked → onDockChainRemoved
Bike Sharing
Bike sharing allows users to rent bikes from sharing areas and return them later.
Rent Bike
Rent/unlock a bike from a sharing area:
- Android
- iOS
- Flutter
// Step 1: Request unlock key
lifecycleScope.launch {
val unlockKey = sdk.requestUnlockKey(
type = MOSADockUnlockKeyType.RentBikeUnlock,
areaId = "area-123"
)
if (unlockKey != null) {
// Step 2: Execute unlock operation
sdk.unlockSmartLock(
unlockKey = unlockKey,
gfaLocationType = MOSADockLocationType.SHARING,
callback = object : MOSADockSDKCallback {
override fun onNFCScanCompleted(mosaTag: MosaTag) {
// NFC scan completed
}
override fun onBleConnecting() {
// BLE connecting
}
override fun onBleConnected() {
// BLE connected
}
override fun onKeySent() {
// Key sent to dock
}
override fun onDockUnlocked() {
// Dock unlocked
}
override fun onDockChainRemoved() {
// Chain removed - bike ready to take, start rental timer
}
override fun onError(error: MosaError) {
// Handle error
}
// Implement other callbacks...
}
)
}
}
Task {
do {
// Step 1: Request unlock key
let unlockKey = try await sdk.requestUnlockKey(
type: .rentBikeUnlock,
areaId: "area-123",
userId: "user-123"
)
// Step 2: Execute unlock operation
try await sdk.unlockSmartLock(
with: unlockKey,
location: .sharing,
delegate: self
)
} catch {
// Handle error
showError("Rent operation failed: \(error)")
}
}
// Delegate callbacks
func onNFCScanCompleted(_ tag: MosaTag) {
// NFC scan completed
}
func onBleConnecting() {
// BLE connecting
}
func onBleConnected() {
// BLE connected
}
func onKeySent() {
// Key sent to dock
}
func onDockUnlocked() {
// Dock unlocked
}
func onDockChainRemoved() {
// Chain removed - bike ready to take, start rental timer
}
func onError(_ error: MosaError) {
// Handle error
}
try {
// Step 1: Request unlock key
final unlockKey = await mosaDockSdk.requestUnlockKey(
type: MosaDockUnlockKeyType.rentBikeUnlock,
areaId: 'area-123',
userId: 'user-123',
);
// Step 2: Execute unlock operation
await mosaDockSdk.handleClientUnlock(
unlockKey: unlockKey,
locationType: MosaDockLocationType.sharing.value,
);
} catch (e) {
// Handle error
showError('Rent operation failed: $e');
}
// Callback handling
mosaDockSdk.dockCallbackEvents.listen((event) {
switch (event.type) {
case DockCallbackType.nfcScanCompleted:
// NFC scan completed
break;
case DockCallbackType.bleConnecting:
// BLE connecting
break;
case DockCallbackType.bleConnected:
// BLE connected
break;
case DockCallbackType.keySent:
// Key sent to dock
break;
case DockCallbackType.dockUnlocked:
// Dock unlocked
break;
case DockCallbackType.dockChainRemoved:
// Chain removed - bike ready to take, start rental timer
break;
case DockCallbackType.error:
// Handle error
break;
default:
break;
}
});
Callback Sequence: onNFCScanCompleted → onBleConnecting → onBleConnected → onKeySent → onDockUnlocked → onDockChainRemoved
Return Bike
Return/lock a bike at a sharing area:
- Android
- iOS
- Flutter
// Step 1: Request lock key
lifecycleScope.launch {
val unlockKey = sdk.requestUnlockKey(
type = MOSADockUnlockKeyType.RentBikeLock,
areaId = "area-123" // areaId from session
)
if (unlockKey != null) {
// Step 2: Execute lock operation
sdk.unlockSmartLock(
unlockKey = unlockKey,
gfaLocationType = MOSADockLocationType.SHARING,
callback = object : MOSADockSDKCallback {
override fun onNFCScanCompleted(mosaTag: MosaTag) {
// NFC scan completed
}
override fun onBleConnecting() {
// BLE connecting
}
override fun onBleConnected() {
// BLE connected
}
override fun onKeySent() {
// Key sent to dock
}
override fun onDockUnlocked() {
// Dock unlocked
}
override fun onDockChainInserted() {
// Chain inserted - bike locked, stop rental timer
}
override fun onDockSecured() {
// Dock secured - rental complete
}
override fun onError(error: MosaError) {
// Handle error
}
// Implement other callbacks...
}
)
}
}
Task {
do {
// Step 1: Request lock key
let unlockKey = try await sdk.requestUnlockKey(
type: .rentBikeLock,
areaId: "area-123", // areaId from session
userId: "user-123"
)
// Step 2: Execute lock operation
try await sdk.unlockSmartLock(
with: unlockKey,
location: .sharing,
delegate: self
)
} catch {
// Handle error
showError("Return operation failed: \(error)")
}
}
// Delegate callbacks
func onNFCScanCompleted(_ tag: MosaTag) {
// NFC scan completed
}
func onBleConnecting() {
// BLE connecting
}
func onBleConnected() {
// BLE connected
}
func onKeySent() {
// Key sent to dock
}
func onDockUnlocked() {
// Dock unlocked
}
func onDockChainInserted() {
// Chain inserted - bike locked, stop rental timer
}
func onDockSecured() {
// Dock secured - rental complete
}
func onError(_ error: MosaError) {
// Handle error
}
try {
// Step 1: Request lock key
final unlockKey = await mosaDockSdk.requestUnlockKey(
type: MosaDockUnlockKeyType.rentBikeLock,
areaId: 'area-123', // areaId from session
userId: 'user-123',
);
// Step 2: Execute lock operation
await mosaDockSdk.handleClientUnlock(
unlockKey: unlockKey,
locationType: MosaDockLocationType.sharing.value,
);
} catch (e) {
// Handle error
showError('Return operation failed: $e');
}
// Callback handling
mosaDockSdk.dockCallbackEvents.listen((event) {
switch (event.type) {
case DockCallbackType.nfcScanCompleted:
// NFC scan completed
break;
case DockCallbackType.bleConnecting:
// BLE connecting
break;
case DockCallbackType.bleConnected:
// BLE connected
break;
case DockCallbackType.keySent:
// Key sent to dock
break;
case DockCallbackType.dockUnlocked:
// Dock unlocked
break;
case DockCallbackType.dockChainInserted:
// Chain inserted - bike locked, stop rental timer
break;
case DockCallbackType.dockSecured:
// Dock secured - rental complete
break;
case DockCallbackType.error:
// Handle error
break;
default:
break;
}
});
Callback Sequence: onNFCScanCompleted → onBleConnecting → onBleConnected → onKeySent → onDockUnlocked → onDockChainInserted → onDockSecured
Next Steps
- Understand SDK Callbacks - Learn how to handle SDK events and update your UI
- Platform-Specific Guides - Detailed platform documentation and advanced configuration
- API Reference - Complete API documentation
For detailed information about unlock keys, including all properties, methods, and validation rules, see the API Reference.