Skip to main content

What you’ll build

In this quickstart you will:
  • Install the Desmo Android SDK via JitPack.
  • Initialize the SDK with your publishable key.
  • Start and stop a delivery session from your Android app.
This page assumes you already have a Desmo account and at least one publishable key that starts with pk_....
If you don’t have a key yet, contact your Desmo representative to have one issued for your project.

Requirements

  • Android 7.0 (API 24) or later
  • Android Gradle Plugin compatible with Kotlin 1.9+
  • Your app is written in Kotlin (the SDK is a Kotlin library)

App permissions

To let Desmo record sessions correctly, your app should request location permissions so sessions can be tied to where the delivery happened. Add these to your AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
On Android 13+, you also need notification permission for the background tracking notification:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
On Android 6.0+ you must also request these at runtime. Later on this page we’ll show how to ask for the permissions using the helpers exposed by the SDK.

1. Install the SDK (JitPack)

The Android SDK is distributed via JitPack from the GitHub repo kubocreate/desmo-android-sdk.

1.1 Add the JitPack repository

In your app project (not the SDK repo), open the root settings.gradle.kts and make sure the dependencyResolutionManagement block includes JitPack:
// settings.gradle.kts (Kotlin DSL)
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven(url = "https://jitpack.io")
    }
}
If you use Groovy and have settings.gradle:
// settings.gradle (Groovy)
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven { url 'https://jitpack.io' }
    }
}
If your project still uses a top-level build.gradle with an allprojects { repositories { ... } } block instead of dependencyResolutionManagement, add the jitpack.io maven entry there:
// Top-level build.gradle (older projects)
allprojects {
    repositories {
        google()
        mavenCentral()
        maven { url 'https://jitpack.io' }
    }
}

1.2 Add the dependency

In your app module build.gradle (usually app/build.gradle):
  1. Remove any old module dependency, for example:
    implementation project(':desmo-android-sdk')
    
  2. Add the JitPack dependency:
    dependencies {
        implementation 'com.github.kubocreate:desmo-android-sdk:v1.0.2'
    }
    
If you use Kotlin DSL for your app module, the equivalent is:
dependencies {
    implementation("com.github.kubocreate:desmo-android-sdk:v1.0.2")
}
Here v1.0.2 is the Git tag for the SDK release on GitHub. When a new version is tagged, update this value to match. If you previously copied any Desmo code directly into your project, or used old Maven Central coordinates or local module references, remove those and keep only the JitPack dependency to avoid duplicate classes.

2. Initialize the SDK

Call Desmo.setup once at app startup, usually in your custom Application class.
import android.app.Application
import io.getdesmo.tracesdk.Desmo
import io.getdesmo.tracesdk.config.DesmoEnvironment

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()

        // Initialize the SDK
        Desmo.setup(
            context = this,
            apiKey = "pk_sandbox_XXXXXXXXXXXXXXXX", // your Desmo publishable key
            environment = DesmoEnvironment.SANDBOX  // use LIVE in production
        )

        // Recommended: Bind to lifecycle for automatic sensor management
        Desmo.bindToProcessLifecycle()
    }
}
Then register your Application class in AndroidManifest.xml:
<application
    android:name=".MyApp"
    ...>
    <!-- activities etc -->
</application>
  • apiKey: your publishable Desmo API key (starts with pk_).
  • environment:
    • DesmoEnvironment.SANDBOX – Desmo sandbox environment.
    • DesmoEnvironment.LIVE – Desmo production environment.
If Desmo.setup fails (for example, if the key does not start with pk_), the SDK will log an error and Desmo.client will remain null. Desmo.bindToProcessLifecycle() automatically manages sensor registration when your app moves between foreground and background. This ensures sensors are re-registered properly after Android throttles them in the background.

Without a Context (no telemetry)

There is also a setup(apiKey, environment) overload that does not take a Context. This is useful in non-Android environments, but it disables on-device telemetry. For typical courier apps you should prefer the setup(context, apiKey, environment) version so telemetry is enabled.

3. Request permissions at runtime

The SDK exposes helpers so you know which permissions are required and whether they are granted:
  • Desmo.getRequiredPermissions() : Array<String>
  • Desmo.hasRequiredPermissions(context) : Boolean
  • Desmo.getMissingPermissions(context) : List<String>
Example with an Activity:
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import io.getdesmo.tracesdk.Desmo

class DeliveryActivity : AppCompatActivity() {

    private val requestPermissionsLauncher =
        registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { _ ->
            // You can re-check Desmo.hasRequiredPermissions(this) here if needed
        }

    private fun ensureDesmoPermissions() {
        if (!Desmo.hasRequiredPermissions(this)) {
            val missing = Desmo.getMissingPermissions(this).toTypedArray()
            requestPermissionsLauncher.launch(missing)
        }
    }
}
Call ensureDesmoPermissions() before you start a session so location is available.

Notification permission (Android 13+)

On Android 13 and later, you need to request notification permission separately for the background tracking notification:
private fun ensureNotificationPermission() {
    if (!Desmo.canShowNotifications(this)) {
        val permissions = Desmo.getForegroundServicePermissions()
        if (permissions.isNotEmpty()) {
            requestPermissionsLauncher.launch(permissions)
        }
    }
}
If notification permission is denied, sessions will still work when the app is in the foreground. However, background tracking won’t function without the notification.

4. Understanding session types

Every session requires a session type that indicates what the driver is doing:
SessionTypeWhen to use
SessionType.PICKUPDriver is collecting a package from a merchant/warehouse
SessionType.DROPDriver is delivering a package to a customer
SessionType.TRANSITDriver is traveling between stops (no specific address)
For PICKUP and DROP sessions, you should provide an address. For TRANSIT sessions, the address is optional.

5. Understanding the Address model

The SDK provides a structured Address model for delivery locations:
import io.getdesmo.tracesdk.models.Address

// Option 1: Structured address (preferred for better insights)
val structuredAddress = Address(
    line1 = "123 Main Street",
    line2 = "Apt 4B",
    city = "San Francisco",
    state = "CA",
    postalCode = "94102",
    country = "US",
    lat = 37.7749,
    lng = -122.4194
)

// Option 2: Raw address string (when you only have unstructured data)
val rawAddress = Address.fromRaw("123 Main Street, San Francisco, CA 94102")
Use structured addresses when possible — they provide better location context for delivery insights.

6. Start a delivery session

Call this when you begin recording a delivery. The SDK uses a crash-safe DesmoResult pattern that guarantees errors will never crash your app.
import androidx.lifecycle.lifecycleScope
import io.getdesmo.tracesdk.Desmo
import io.getdesmo.tracesdk.models.Address
import io.getdesmo.tracesdk.models.SessionType
import kotlinx.coroutines.launch

fun startDeliverySession() {
    val client = Desmo.client
    if (client == null) {
        println("Desmo SDK is not configured")
        return
    }

    lifecycleScope.launch {
        client.startSession(
                deliveryId = "DELIVERY_123",
            sessionType = SessionType.DROP,
            address = Address.fromRaw("123 Main Street, San Francisco")
        ).onSuccess { session ->
            println("Started session: ${session.sessionId}")
        }.onFailure { error ->
            // SDK errors never crash your app - handle gracefully
            println("Failed to start session: $error")
        }
    }
}

Required parameters

ParameterTypeDescription
deliveryIdStringYour internal delivery/order identifier
sessionTypeSessionTypePICKUP, DROP, or TRANSIT

Optional parameters

ParameterTypeDescription
addressAddress?Delivery address (required for PICKUP/DROP, optional for TRANSIT)
externalRiderIdString?Your system’s driver/rider ID
startLocationStartLocation?Override the starting GPS position (auto-captured if not provided)

Full example with all parameters

import io.getdesmo.tracesdk.models.Address
import io.getdesmo.tracesdk.models.SessionType
import io.getdesmo.tracesdk.models.StartLocation

client.startSession(
    deliveryId = "ORDER_12345",
    sessionType = SessionType.DROP,
    externalRiderId = "DRIVER_789",
    address = Address(
        line1 = "456 Oak Avenue",
        city = "San Francisco",
        state = "CA",
        postalCode = "94102",
        country = "US"
    ),
    startLocation = StartLocation(lat = 37.7749, lng = -122.4194)
).onSuccess { session ->
    // Session started successfully
}.onFailure { error ->
    // Handle error (delivery flow should continue)
}
Behind the scenes, the SDK:
  • Calls Desmo’s backend to create a session.
  • Starts collecting telemetry on-device (when initialized with a Context).
  • Starts a foreground service for background tracking.
  • Begins batching telemetry to Desmo while the session is recording.

7. Stop a delivery session

Call this when the delivery is complete:
fun stopDeliverySession() {
    val client = Desmo.client
    if (client == null) {
        println("Desmo SDK is not configured")
        return
    }

    lifecycleScope.launch {
        client.stopSession()
            .onSuccess { session ->
            println("Stopped session: ${session.sessionId}")
            }
            .onFailure { error ->
                // Consider retrying - telemetry is saved locally
                println("Failed to stop session: $error")
        }
    }
}
When you stop the session, the SDK:
  • Flushes any remaining telemetry.
  • Notifies the backend that the session is complete.
  • Stops the background tracking service and removes the notification.
  • Lets Desmo’s workers start processing PoD intelligence for that session.

8. The DesmoResult pattern

The SDK uses a crash-safe DesmoResult pattern instead of throwing exceptions. This guarantees SDK errors will never crash your app.
// Pattern 1: Fluent callbacks (recommended)
client.startSession(...)
    .onSuccess { session -> /* use session */ }
    .onFailure { error -> /* log error, continue delivery */ }

// Pattern 2: Explicit when
when (val result = client.startSession(...)) {
    is DesmoResult.Success -> useSession(result.data)
    is DesmoResult.Failure -> logError(result.error)
}

// Pattern 3: Fire and forget
val session = client.startSession(...).getOrNull() // null if failed
Always handle onFailure gracefully. The driver’s delivery flow should continue even if the SDK encounters an error.

9. Background operation

The SDK automatically keeps tracking when the app goes to background. This is critical for delivery apps where drivers put their phone in their pocket.

How it works

When you call startSession(), the SDK automatically:
  1. Starts a foreground service to keep the app alive
  2. Shows a notification (required by Android) indicating tracking is active
  3. Continues collecting location and sensor data even when the screen is off
When you call stopSession(), the service stops and the notification disappears.

Default notification

By default, the notification uses a generic location icon with text:
  • Title: “Recording Active”
  • Text: “Tracking delivery in progress”

Customizing the notification (optional)

To show your own branding, call configureForegroundService() after setup:
Desmo.setup(this, apiKey, environment)
Desmo.bindToProcessLifecycle()

// Optional: Customize the notification with your branding
Desmo.configureForegroundService(
    title = "SwiftDrop Delivery",
    text = "Recording your route...",
    smallIconResId = R.drawable.ic_delivery
)
For full control, you can provide your own Notification object:
val notification = NotificationCompat.Builder(this, "my_channel")
    .setContentTitle("SwiftDrop Active")
    .setContentText("3 stops remaining")
    .setSmallIcon(R.drawable.ic_swiftdrop)
    .setColor(0xFF00B140.toInt())
    .build()

Desmo.configureForegroundService(notification)
If you provide a custom notification, you must create the notification channel yourself before starting a session.

Next steps

From here: