Skip to main content

Android SDK Examples & Troubleshooting

Complete implementation examples and solutions to common integration challenges.

Complete Implementation Example

DoorstepAI SDK Test App

This is a complete example of a test app that demonstrates all SDK functionality using Jetpack Compose:

MainActivity.kt
package com.doorstepai.doorstepsdktestapp

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.doorstepai.doorstepsdktestapp.ui.theme.DoorstepSDKTestAppTheme
import com.doorstepai.sdks.tracking.DoorstepAI
import com.doorstepai.sdks.tracking.AddressType
import kotlinx.coroutines.launch

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope

/**
* DoorstepAI SDK Permission Utilities
*
* This class provides utilities for handling permissions required by the DoorstepAI SDK.
* This is needed as certain permissions need runtime approval
* Copy this entire class into your app to handle SDK permissions.
*/
object DoorstepAIPermissionUtils {

fun getRequiredPermissions(): List<String> {
val permissions = mutableListOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_NETWORK_STATE
)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
permissions.add(Manifest.permission.ACTIVITY_RECOGNITION)
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
permissions.add(Manifest.permission.FOREGROUND_SERVICE)
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissions.add(Manifest.permission.POST_NOTIFICATIONS)
}

return permissions
}

fun hasAllPermissions(context: Context): Boolean {
return getRequiredPermissions().all { permission ->
ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
}
}

fun getMissingPermissions(context: Context): List<String> {
return getRequiredPermissions().filter { permission ->
ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED
}
}

/**
* Get permission rationale text for showing to users
* @return User-friendly explanation of why permissions are needed
*/
fun getPermissionRationale(): String {
return "The DoorstepAI SDK needs these permissions to provide delivery waypoints"
}
}

class MainActivity : ComponentActivity() {
companion object {
private const val FIXED_API_TOKEN = "api_key" // Replace with your actual API token
}

private var permissionsGranted by mutableStateOf(false)
private var sdkInitialized by mutableStateOf(false)

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

setContent {
DoorstepSDKTestAppTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
DoorstepSDKTestScreen(
context = this,
permissionsGranted = permissionsGranted,
sdkInitialized = sdkInitialized,
modifier = Modifier.padding(innerPadding)
)
}
}
}
}

fun checkPermissionsAndInitialize() {
if (DoorstepAIPermissionUtils.hasAllPermissions(this)) {
permissionsGranted = true
initializeSDK()
} else {
permissionsGranted = false
}
}

private fun initializeSDK() {
try {
DoorstepAI.init(
context = this,
notificationTitle = "Tracking...",
notificationText = "Tracking your delivery"
) { result ->
result.fold(
onSuccess = {
try {
DoorstepAI.setAPIKey(FIXED_API_TOKEN)
println("✅ DoorstepAI SDK initialized successfully + API token set")
sdkInitialized = true
} catch (e: Exception) {
println("❌ Failed to set API token: ${e.message}")
sdkInitialized = false
}
},
onFailure = { error ->
println("❌ SDK initialization failed: ${error.message}")
sdkInitialized = false
}
)
}
} catch (e: Exception) {
println("❌ Error during SDK initialization: ${e.message}")
sdkInitialized = false
}
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DoorstepSDKTestScreen(
context: MainActivity,
permissionsGranted: Boolean,
sdkInitialized: Boolean,
modifier: Modifier = Modifier
) {
val scope = rememberCoroutineScope()
var statusText by remember { mutableStateOf("Checking permissions...") }
var currentDeliveryId by remember { mutableStateOf("") }
var placeId by remember { mutableStateOf("") }
var plusCode by remember { mutableStateOf("") }
var eventName by remember { mutableStateOf("") }
var isDeliveryActive by remember { mutableStateOf(false) }
var activeDeliveryType by remember { mutableStateOf("") }

// Address fields
var streetNumber by remember { mutableStateOf("") }
var route by remember { mutableStateOf("") }
var subPremise by remember { mutableStateOf("") }
var locality by remember { mutableStateOf("") }
var administrativeArea by remember { mutableStateOf("") }
var postalCode by remember { mutableStateOf("") }

// Permission launcher - automatically trigger when needed
val permissionLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val allGranted = permissions.all { it.value }
if (allGranted) {
statusText = "All permissions granted! Initializing SDK..."
context.checkPermissionsAndInitialize()
} else {
val deniedPermissions = permissions.filter { !it.value }.keys
statusText = "Some permissions were denied: ${deniedPermissions.joinToString(", ")}"
}
}

LaunchedEffect(permissionsGranted) {
if (!permissionsGranted) {
val missingPermissions = DoorstepAIPermissionUtils.getMissingPermissions(context)
if (missingPermissions.isNotEmpty()) {
permissionLauncher.launch(missingPermissions.toTypedArray())
}
}
}

LaunchedEffect(permissionsGranted, sdkInitialized) {
statusText = when {
!permissionsGranted -> "Requesting permissions for DoorstepAI SDK..."
!sdkInitialized -> "Initializing DoorstepAI SDK..."
else -> "DoorstepAI SDK ready"
}
}

Column(
modifier = modifier
.fillMaxSize()
.padding(16.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "doorstep.ai DropOff SDK Test App",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(bottom = 16.dp)
)

Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = when {
!permissionsGranted -> MaterialTheme.colorScheme.errorContainer
!sdkInitialized -> MaterialTheme.colorScheme.tertiaryContainer
else -> MaterialTheme.colorScheme.primaryContainer
}
)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = statusText,
fontSize = 14.sp,
fontWeight = FontWeight.Medium
)

if (!permissionsGranted) {
Spacer(modifier = Modifier.height(8.dp))
Text(
text = DoorstepAIPermissionUtils.getPermissionRationale(),
fontSize = 12.sp,
color = MaterialTheme.colorScheme.onErrorContainer
)
}
}
}

Spacer(modifier = Modifier.height(16.dp))

if (permissionsGranted && sdkInitialized) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer
)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Delivery ID",
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(bottom = 8.dp)
)

OutlinedTextField(
value = currentDeliveryId,
onValueChange = { currentDeliveryId = it },
label = { Text("Enter Delivery ID") },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
}
}

Spacer(modifier = Modifier.height(16.dp))

if (isDeliveryActive) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.tertiaryContainer
)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Active Delivery",
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onTertiaryContainer
)
Text(
text = "Type: $activeDeliveryType",
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onTertiaryContainer
)
Text(
text = "ID: $currentDeliveryId",
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onTertiaryContainer
)

Spacer(modifier = Modifier.height(8.dp))

Button(
onClick = {
try {
DoorstepAI.stopDelivery(currentDeliveryId)
statusText = "Delivery stopped for: $currentDeliveryId"
isDeliveryActive = false
activeDeliveryType = ""
} catch (e: Exception) {
statusText = "Error stopping delivery: ${e.message}"
}
},
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.error
)
) {
Text("Stop Active Delivery")
}
}
}

Spacer(modifier = Modifier.height(16.dp))
}

if (!isDeliveryActive) {
Text(
text = "Start New Delivery",
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.align(Alignment.Start)
)

Spacer(modifier = Modifier.height(8.dp))

OutlinedTextField(
value = placeId,
onValueChange = { placeId = it },
label = { Text("Place ID") },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)

Button(
onClick = {
if (currentDeliveryId.isNotEmpty() && placeId.isNotEmpty()) {
try {
DoorstepAI.startDeliveryByPlaceID(placeId, currentDeliveryId) { result ->
result.fold(
onSuccess = { message ->
statusText = message
isDeliveryActive = true
activeDeliveryType = "Place ID"
},
onFailure = { error ->
statusText = "Failed to start delivery: ${error.message}"
}
)
}
} catch (e: Exception) {
statusText = "Error starting delivery: ${e.message}"
}
} else {
statusText = "Please enter both Delivery ID and Place ID"
}
},
modifier = Modifier.fillMaxWidth(),
enabled = currentDeliveryId.isNotEmpty() && placeId.isNotEmpty()
) {
Text("Start Delivery by Place ID")
}

Spacer(modifier = Modifier.height(8.dp))

OutlinedTextField(
value = plusCode,
onValueChange = { plusCode = it },
label = { Text("Plus Code") },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)

Button(
onClick = {
if (currentDeliveryId.isNotEmpty() && plusCode.isNotEmpty()) {
try {
DoorstepAI.startDeliveryByPlusCode(plusCode, currentDeliveryId) { result ->
result.fold(
onSuccess = { message ->
statusText = message
isDeliveryActive = true
activeDeliveryType = "Plus Code"
},
onFailure = { error ->
statusText = "Failed to start delivery: ${error.message}"
}
)
}
} catch (e: Exception) {
statusText = "Error starting delivery: ${e.message}"
}
} else {
statusText = "Please enter both Delivery ID and Plus Code"
}
},
modifier = Modifier.fillMaxWidth(),
enabled = currentDeliveryId.isNotEmpty() && plusCode.isNotEmpty()
) {
Text("Start Delivery by Plus Code")
}

Spacer(modifier = Modifier.height(8.dp))

Text(
text = "Address Components",
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
modifier = Modifier.align(Alignment.Start)
)

OutlinedTextField(
value = streetNumber,
onValueChange = { streetNumber = it },
label = { Text("Street Number") },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)

OutlinedTextField(
value = route,
onValueChange = { route = it },
label = { Text("Route") },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)

OutlinedTextField(
value = subPremise,
onValueChange = { subPremise = it },
label = { Text("Sub Premise (Apt/Suite)") },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)

OutlinedTextField(
value = locality,
onValueChange = { locality = it },
label = { Text("Locality (City)") },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)

OutlinedTextField(
value = administrativeArea,
onValueChange = { administrativeArea = it },
label = { Text("Administrative Area (State)") },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)

OutlinedTextField(
value = postalCode,
onValueChange = { postalCode = it },
label = { Text("Postal Code") },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)

Button(
onClick = {
if (currentDeliveryId.isNotEmpty() && streetNumber.isNotEmpty() && route.isNotEmpty()) {
try {
val address = AddressType(
streetNumber = streetNumber,
route = route,
subPremise = subPremise,
locality = locality,
administrativeAreaLevel1 = administrativeArea,
postalCode = postalCode
)
DoorstepAI.startDeliveryByAddressType(address, currentDeliveryId) { result ->
result.fold(
onSuccess = { message ->
statusText = message
isDeliveryActive = true
activeDeliveryType = "Address"
},
onFailure = { error ->
statusText = "Failed to start delivery: ${error.message}"
}
)
}
} catch (e: Exception) {
statusText = "Error starting delivery: ${e.message}"
}
} else {
statusText = "Please enter Delivery ID, Street Number, and Route"
}
},
modifier = Modifier.fillMaxWidth(),
enabled = currentDeliveryId.isNotEmpty() && streetNumber.isNotEmpty() && route.isNotEmpty()
) {
Text("Start Delivery by Address")
}
}

Spacer(modifier = Modifier.height(16.dp))

Text(
text = "Events",
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.align(Alignment.Start)
)

OutlinedTextField(
value = eventName,
onValueChange = { eventName = it },
label = { Text("Event Name") },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)

Button(
onClick = {
if (currentDeliveryId.isNotEmpty() && eventName.isNotEmpty()) {
try {
DoorstepAI.newEvent(eventName, currentDeliveryId) { result ->
result.fold(
onSuccess = { message ->
statusText = "Event created: $message"
},
onFailure = { error ->
statusText = "Failed to create event: ${error.message}"
}
)
}
} catch (e: Exception) {
statusText = "Error creating event: ${e.message}"
}
} else {
statusText = "Please enter both Delivery ID and Event Name"
}
},
modifier = Modifier.fillMaxWidth(),
enabled = currentDeliveryId.isNotEmpty() && eventName.isNotEmpty()
) {
Text("Create Event")
}
}

Spacer(modifier = Modifier.height(32.dp))
}
}

Troubleshooting

Common Issues and Solutions

1. SDK Initialization Failures

Problem: SDK fails to initialize with callback errors.

Solutions:

class DebugInitializer {
fun initializeWithDebug(context: Context) {
Log.d("DoorstepAI", "Starting SDK initialization...")

DoorstepAI.init(
context = context.applicationContext, // Use application context
notificationTitle = "Tracking...",
notificationText = "Tracking your delivery"
) { result ->
result.fold(
onSuccess = { message ->
Log.i("DoorstepAI", "✅ Initialization successful: $message")

// Verify API key is set
val apiKey = BuildConfig.DOORSTEP_API_KEY
if (apiKey.isNotBlank() && apiKey != "YOUR_API_KEY_HERE") {
DoorstepAI.setAPIKey(apiKey)
Log.i("DoorstepAI", "✅ API key set successfully")
} else {
Log.e("DoorstepAI", "❌ Invalid API key")
}
},
onFailure = { error ->
Log.e("DoorstepAI", "❌ Initialization failed", error)

// Check common failure reasons
checkInitializationRequirements(context)
}
)
}
}

private fun checkInitializationRequirements(context: Context) {
// Check if context is valid
Log.d("DoorstepAI", "Context: ${context.javaClass.simpleName}")

// Check permissions in manifest
val requiredPermissions = arrayOf(
"android.permission.ACCESS_FINE_LOCATION",
"android.permission.FOREGROUND_SERVICE"
)

requiredPermissions.forEach { permission ->
val hasPermission = context.checkSelfPermission(permission) ==
PackageManager.PERMISSION_GRANTED
Log.d("DoorstepAI", "Permission $permission: $hasPermission")
}
}
}

2. Runtime Permission Issues

Problem: Location or activity recognition permissions are denied.

Solutions:

class AdvancedPermissionManager(private val activity: Activity) {

fun requestPermissionsWithRationale() {
when {
// Check if we should show rationale
shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION) -> {
showPermissionRationaleDialog()
}

else -> {
requestBasicPermissions()
}
}
}

private fun showPermissionRationaleDialog() {
AlertDialog.Builder(activity)
.setTitle("Location Permission Required")
.setMessage("This app needs location access to provide accurate delivery tracking and intelligence.")
.setPositiveButton("Grant Permission") { _, _ ->
requestBasicPermissions()
}
.setNegativeButton("Cancel") { dialog, _ ->
dialog.dismiss()
}
.show()
}

private fun requestBasicPermissions() {
val missingPermissions = DoorstepAIPermissionUtils.getMissingPermissions(activity)
if (missingPermissions.isNotEmpty()) {
ActivityCompat.requestPermissions(
activity,
missingPermissions.toTypedArray(),
PERMISSION_REQUEST_CODE
)
}
}

companion object {
private const val PERMISSION_REQUEST_CODE = 1001
}
}

3. Network Connectivity Issues

Problem: SDK fails to communicate with servers.

Solutions:

class NetworkDiagnostics(private val context: Context) {

fun checkNetworkConnectivity(): NetworkStatus {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE)
as ConnectivityManager

return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val network = connectivityManager.activeNetwork
val capabilities = connectivityManager.getNetworkCapabilities(network)

when {
capabilities == null -> NetworkStatus.NO_CONNECTION
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> NetworkStatus.WIFI
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> NetworkStatus.CELLULAR
else -> NetworkStatus.OTHER
}
} else {
@Suppress("DEPRECATION")
val networkInfo = connectivityManager.activeNetworkInfo
if (networkInfo?.isConnected == true) {
NetworkStatus.CONNECTED_LEGACY
} else {
NetworkStatus.NO_CONNECTION
}
}
}

fun diagnoseNetworkIssues() {
val status = checkNetworkConnectivity()

when (status) {
NetworkStatus.NO_CONNECTION -> {
Log.e("NetworkDiag", "❌ No network connection available")
}
NetworkStatus.WIFI -> {
Log.i("NetworkDiag", "✅ Connected via WiFi")
testAPIConnectivity()
}
NetworkStatus.CELLULAR -> {
Log.i("NetworkDiag", "✅ Connected via Cellular")
testAPIConnectivity()
}
else -> {
Log.i("NetworkDiag", "✅ Network connection available")
}
}
}

private fun testAPIConnectivity() {
// Test connectivity to DoorstepAI servers
// This is a simplified example - actual implementation would test API endpoints
Log.d("NetworkDiag", "Testing API connectivity...")
}
}

enum class NetworkStatus {
NO_CONNECTION, WIFI, CELLULAR, OTHER, CONNECTED_LEGACY
}

Testing Checklist

Before releasing your Android integration:

  • ✅ SDK initializes successfully in Activity
  • ✅ API key is valid and securely stored
  • ✅ All required permissions are declared in AndroidManifest.xml
  • ✅ Runtime permissions are properly requested and handled
  • ✅ Notification permission is granted (Android 13+)
  • ✅ Error handling is implemented for all SDK methods
  • ✅ Delivery IDs are unique and meaningful