Documentation Index
Fetch the complete documentation index at: https://docu.truemetrics.cloud/llms.txt
Use this file to discover all available pages before exploring further.
The Truemetrics SDK provides advanced sensor data collection and analytics capabilities for Android
applications. This guide covers all aspects of integrating and using the SDK effectively.
Table of Contents
Installation
Add the following to your root-level build.gradle file (or settings.gradle):
repositories {
maven {
url "https://github.com/TRUE-Metrics-io/truemetrics_android_SDK_p_maven/raw/"
}
}
Then add the dependency to your app-level build.gradle:
dependencies {
implementation 'io.truemetrics:truemetricssdk:1.5.4'
}
Quick Start
1. Initialize the SDK in your Application class
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
val config = SdkConfiguration.Builder("YOUR_API_KEY").build()
TruemetricsSdk.init(this, config)
}
}
2. Start and stop recordings
val sdk = TruemetricsSdk.getInstance()
// Start recording sensor data
sdk.startRecording()
// Stop recording
sdk.stopRecording()
// Check recording status
if (sdk.isRecordingInProgress()) {
// Recording is active
}
Initialization Lifecycle
TruemetricsSdk.init() returns immediately but the SDK is not ready for use until it reaches Status.Initialized. Initialization runs asynchronously in the background: it binds a foreground service, then fetches the configuration from the backend.
State Transitions
Uninitialized ──► Initializing ──► Initialized
│
├──► Error(AUTHENTICATION_ERROR) (invalid/expired/revoked API key)
│
└──► stays in Initializing, retrying every 30s
(network unreachable, 5xx from backend, timeouts)
How Long To Wait
| Condition | Typical time to Initialized |
|---|
| Good network | 1–3 seconds |
| Slow/lossy network | up to tens of seconds |
| Backend 5xx or offline | indefinite — retries every 30 seconds until it succeeds |
| Invalid API key | fails fast (under ~2 seconds) with Status.Error(AUTHENTICATION_ERROR) |
There is no hard timeout on initialization. Under poor connectivity the SDK stays in Status.Initializing and keeps retrying. Do not impose an arbitrary client-side timeout — either wait for Status.Initialized/Status.Error, or gate UI on getDeviceId() becoming non-null.
Checking Initialization Status
Recommended — observe the status flow and wait for Status.Initialized:
import io.truemetrics.truemetricssdk.engine.state.Status
import kotlinx.coroutines.flow.first
suspend fun awaitSdkReady(): String {
val sdk = TruemetricsSdk.getInstance()
val ready = sdk.observeSdkStatus().first { status ->
status is Status.Initialized ||
status is Status.RecordingInProgress ||
status is Status.DelayedStart ||
status is Status.Error
}
if (ready is Status.Error) {
throw IllegalStateException("SDK init failed: ${ready.errorCode} ${ready.message}")
}
return sdk.getDeviceId()!!
}
Non-blocking — react to state changes as they arrive:
sdk.observeSdkStatus().collect { status ->
when (status) {
is Status.Initializing -> showSpinner()
is Status.Initialized -> enableStartButton(status.deviceId)
is Status.Error -> showError(status.errorCode, status.message)
else -> {}
}
}
Quick synchronous check — getDeviceId() returns null until initialization completes:
if (sdk.getDeviceId() != null) {
// SDK is initialized
}
Calling Other APIs Before Initialization Completes
startRecording() — safe to call immediately after init(). If the service is not yet bound, the request is queued and executed once binding completes. However, if called after the service binds but before the config fetch succeeds, it will transition the SDK to Status.Error(CONFIG_ERROR). To avoid this, either await Status.Initialized first, or rely on the auto-start configuration (see Configuration).
logMetadata() — ignored with a log warning if called before initialization. Wait for Status.Initialized before logging.
getDeviceId(), getActiveConfig(), getUploadStatistics(), getSensorStatistics() — return null until initialization completes.
Terminal vs. Transient Failures
| Status | Meaning | Your action |
|---|
Status.Initializing (stuck) | Backend unreachable or returning 5xx; SDK auto-retries every 30s | Wait, or surface a “connecting” indicator. No need to call init() again. |
Status.Error(AUTHENTICATION_ERROR) | API key invalid, expired, or revoked. SDK has deinitialized itself. | Do not retry with the same key. Fix the key, then call init() again. |
Status.Error(CONFIG_ERROR) | startRecording() was called before config was loaded | Await Status.Initialized before calling startRecording(). |
Status.Error(MISSING_NOTIFICATION_PERMISSION) | Android 13+ notification permission not granted | Request POST_NOTIFICATIONS, then re-init. |
Configuration
The SDK is configured using the SdkConfiguration.Builder class.
Basic Configuration
val config = SdkConfiguration.Builder("YOUR_API_KEY").build()
Custom Foreground Notification
Customize the notification shown when the SDK is running as a foreground service using the SDK’s notification channel:
class CustomNotificationFactory : ForegroundNotificationFactory {
override fun createNotification(context: Context): Notification {
// Use SDK's notification channel ID
return NotificationCompat.Builder(context, "FOREGROUND_SERVICE")
.setContentTitle("Truemetrics SDK Running")
.setContentText("Collecting sensor data...")
.setSmallIcon(R.drawable.ic_sensors)
.setOngoing(true)
.build()
}
}
val config = SdkConfiguration.Builder("YOUR_API_KEY")
.foregroundNotificationFactory(CustomNotificationFactory())
.build()
Core Features
Recording Management
val sdk = TruemetricsSdk.getInstance()
// Start recording
sdk.startRecording()
// Stop recording
sdk.stopRecording()
// Check recording status
val isRecording = sdk.isRecordingInProgress()
val isStopped = sdk.isRecordingStopped()
// Get recording start time
val startTime = sdk.getRecordingStartTime() // Returns timestamp in milliseconds
Device ID
Get the unique device identifier (available after initialization):
val deviceId = sdk.getDeviceId()
// Or get from Status
sdk.observeSdkStatus().collect { status ->
when (status) {
is Status.Initialized -> println("Device ID: ${status.deviceId}")
is Status.RecordingInProgress -> println("Device ID: ${status.deviceId}")
else -> {}
}
}
The device ID may rotate automatically based on a server-configured TTL. When expired, a new ID is generated transparently on the next initialization.
Log standardized delivery/pickup event metadata:
import io.truemetrics.truemetricssdk.metadata.StandardMetadata
sdk.logMetadata(StandardMetadata(
eventTime = "2025-01-15T14:30:00",
eventType = "delivery_successful",
deliveryId = "parcel_123",
tourId = "tour_456",
waypointLatitude = "52.5200",
waypointLongitude = "13.4050",
referenceLatitude = "52.5201",
referenceLongitude = "13.4051",
address = "Pflanzstrasse 5, 10762 Berlin",
extra = mapOf("type_vehicle" to "car")
))
The map-based logMetadata(Map<String, String>) overload is deprecated. Use logMetadata(StandardMetadata) instead.
For advanced metadata with templates and tags, see Metadata Guide.
Sensor Management
// Enable/disable all sensors
sdk.setAllSensorsEnabled(true)
// Check if sensors are enabled
val enabled = sdk.getAllSensorsEnabled()
// Observe available sensors
sdk.sensorInfo.collect { sensors ->
sensors.forEach { sensor ->
println("Sensor: ${sensor.sensorName}, Status: ${sensor.sensorStatus}")
println("Frequency: ${sensor.frequency} Hz")
if (sensor.missingPermissions.isNotEmpty()) {
println("Missing permissions: ${sensor.missingPermissions}")
}
}
}
Complete Cleanup
Warning: After calling deinitialize(), the SDK instance becomes unusable. You must call
init() again to use the SDK.
SDK Status
Monitor the SDK state using observeSdkStatus():
sdk.observeSdkStatus().collect { status ->
when (status) {
is Status.Uninitialized -> {
// SDK not yet initialized
}
is Status.Initializing -> {
// SDK is fetching config from server
// Retries automatically on network errors or server 5xx
}
is Status.Initialized -> {
// SDK initialized, deviceId available
val deviceId = status.deviceId
}
is Status.RecordingInProgress -> {
// Recording is active
val deviceId = status.deviceId
}
is Status.RecordingStopped -> {
// Recording has stopped
}
is Status.DelayedStart -> {
// Waiting for auto-start delay
val deviceId = status.deviceId
val delayMs = status.delayMs
}
is Status.TrafficLimitReached -> {
// Traffic limit reached
}
is Status.ReadingsDatabaseFull -> {
// Device storage full
}
is Status.Error -> {
// SDK encountered an error
val errorCode = status.errorCode
val message = status.message
}
is Status.AskForPermissions -> {
// SDK requesting permissions
val permissions = status.permissions
}
}
}
Error Codes
| Error Code | Description |
|---|
| AUTHENTICATION_ERROR | API key is not valid, expired or revoked |
| UPLOAD_ERROR | Recordings couldn’t be uploaded after exhausting all attempts |
| STORAGE_FULL | Device storage is full which prevents saving sensor readings |
| MISSING_NOTIFICATION_PERMISSION | Notification permission not granted, foreground service cannot start |
| CONFIG_ERROR | Configuration error |
| TRAFFIC_USED_UP | All allotted traffic has been used |
| SENSORS_NOT_WORKING | Some sensors are not working. Check if permissions are missing |
Statistics API
Upload Statistics
Monitor upload health:
val uploadStats = sdk.getUploadStatistics()
if (uploadStats != null) {
println("Successful uploads: ${uploadStats.successfulUploadsCount}")
println("Last upload: ${uploadStats.lastSuccessfulUploadTimestamp}")
}
Sensor Statistics
Get detailed sensor data quality:
val sensorStats = sdk.getSensorStatistics()
sensorStats?.forEach { stat ->
println("Sensor: ${stat.sensorName}")
println("Configured: ${stat.configuredFrequencyHz} Hz")
println("Actual: ${stat.actualFrequencyHz} Hz")
println("Quality: ${stat.quality}")
}
Quality levels:
- EXCELLENT: 95-100% of configured frequency
- GOOD: 80-95%
- POOR: 50-80%
- BAD: less than 50%
- UNKNOWN: no data or not recording
Best Practices
1. Initialization
- Always initialize in your
Application class
- Use the application context, not activity context
- Initialize once and reuse the singleton instance
2. Error Handling
sdk.observeSdkStatus().collect { status ->
when (status) {
is Status.Error -> {
Log.e("SDK", "Error: ${status.errorCode} - ${status.message}")
}
is Status.ReadingsDatabaseFull -> {
Log.w("SDK", "Storage full")
}
else -> {}
}
}
3. Monitoring SDK Health
fun checkSdkHealth() {
val uploadStats = sdk.getUploadStatistics()
if (uploadStats != null && uploadStats.successfulUploadsCount == 0) {
Log.w("SDK", "No successful uploads yet")
}
val sensorStats = sdk.getSensorStatistics()
sensorStats?.filter { it.quality == SensorDataQuality.BAD }?.forEach {
Log.w("SDK", "Bad quality for: ${it.sensorName}")
}
}
API Reference
TruemetricsSdk
| Method | Return Type | Description |
|---|
init(context, config) | TruemetricsSdk | Initialize SDK (static) |
getInstance() | TruemetricsSdk | Get singleton instance (static) |
startRecording() | Unit | Start sensor recording |
stopRecording() | Unit | Stop sensor recording |
isRecordingInProgress() | Boolean | Check if recording active |
isRecordingStopped() | Boolean | Check if recording stopped |
getRecordingStartTime() | Long | Get recording start timestamp |
getDeviceId() | String? | Get unique device identifier |
logMetadata(standardMetadata) | Unit | Log standardized event metadata |
logMetadata(payload) (deprecated) | Unit | Log custom metadata map |
setAllSensorsEnabled(enabled) | Unit | Enable/disable all sensors |
getAllSensorsEnabled() | Boolean | Get sensor enable status |
getActiveConfig() | Configuration? | Get active backend configuration |
getUploadStatistics() | UploadStatistics? | Get upload stats |
getSensorStatistics() | List<SensorStatistics>? | Get sensor stats |
deinitialize() | Unit | Shutdown SDK completely |
Observable Flows
| Property/Method | Type | Description |
|---|
sensorInfo | StateFlow<Iterable<SensorInfo>> | Available sensors info |
sdkStatus | StateFlow<Status> | SDK operational status |
observeSdkStatus() | Flow<Status> | Observe SDK status changes |
getActiveConfigFlow() | Flow<Configuration>? | Observe backend config changes |
Data Classes
UploadStatistics
| Property | Type | Description |
|---|
successfulUploadsCount | Int | Total successful uploads |
lastSuccessfulUploadTimestamp | Long? | Last upload timestamp |
SensorStatistics
| Property | Type | Description |
|---|
sensorName | SensorName | Sensor identifier |
configuredFrequencyHz | Float | Configured frequency |
actualFrequencyHz | Float | Actual measured frequency |
quality | SensorDataQuality | Quality assessment |
SensorInfo
| Property | Type | Description |
|---|
sensorName | SensorName | Sensor identifier |
sensorStatus | SensorStatus | Status: ON, OFF, or NA |
frequency | Float | Polling frequency in Hz |
missingPermissions | List<String> | Required permissions not granted |