Skip to main content

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

ConditionTypical time to Initialized
Good network1–3 seconds
Slow/lossy networkup to tens of seconds
Backend 5xx or offlineindefinite — retries every 30 seconds until it succeeds
Invalid API keyfails 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 checkgetDeviceId() 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

StatusMeaningYour action
Status.Initializing (stuck)Backend unreachable or returning 5xx; SDK auto-retries every 30sWait, 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 loadedAwait Status.Initialized before calling startRecording().
Status.Error(MISSING_NOTIFICATION_PERMISSION)Android 13+ notification permission not grantedRequest 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.

Metadata Logging

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

sdk.deinitialize()
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 CodeDescription
AUTHENTICATION_ERRORAPI key is not valid, expired or revoked
UPLOAD_ERRORRecordings couldn’t be uploaded after exhausting all attempts
STORAGE_FULLDevice storage is full which prevents saving sensor readings
MISSING_NOTIFICATION_PERMISSIONNotification permission not granted, foreground service cannot start
CONFIG_ERRORConfiguration error
TRAFFIC_USED_UPAll allotted traffic has been used
SENSORS_NOT_WORKINGSome 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

MethodReturn TypeDescription
init(context, config)TruemetricsSdkInitialize SDK (static)
getInstance()TruemetricsSdkGet singleton instance (static)
startRecording()UnitStart sensor recording
stopRecording()UnitStop sensor recording
isRecordingInProgress()BooleanCheck if recording active
isRecordingStopped()BooleanCheck if recording stopped
getRecordingStartTime()LongGet recording start timestamp
getDeviceId()String?Get unique device identifier
logMetadata(standardMetadata)UnitLog standardized event metadata
logMetadata(payload) (deprecated)UnitLog custom metadata map
setAllSensorsEnabled(enabled)UnitEnable/disable all sensors
getAllSensorsEnabled()BooleanGet sensor enable status
getActiveConfig()Configuration?Get active backend configuration
getUploadStatistics()UploadStatistics?Get upload stats
getSensorStatistics()List<SensorStatistics>?Get sensor stats
deinitialize()UnitShutdown SDK completely

Observable Flows

Property/MethodTypeDescription
sensorInfoStateFlow<Iterable<SensorInfo>>Available sensors info
sdkStatusStateFlow<Status>SDK operational status
observeSdkStatus()Flow<Status>Observe SDK status changes
getActiveConfigFlow()Flow<Configuration>?Observe backend config changes

Data Classes

UploadStatistics

PropertyTypeDescription
successfulUploadsCountIntTotal successful uploads
lastSuccessfulUploadTimestampLong?Last upload timestamp

SensorStatistics

PropertyTypeDescription
sensorNameSensorNameSensor identifier
configuredFrequencyHzFloatConfigured frequency
actualFrequencyHzFloatActual measured frequency
qualitySensorDataQualityQuality assessment

SensorInfo

PropertyTypeDescription
sensorNameSensorNameSensor identifier
sensorStatusSensorStatusStatus: ON, OFF, or NA
frequencyFloatPolling frequency in Hz
missingPermissionsList<String>Required permissions not granted