Core Integration Guide
This guide details the complete developer workflow for initializing the Veg Sahayyak SDK, configuring speed thresholds, starting foreground tracking, and consuming the real-time telematics StateFlow.
⚡ 1. SDK Initialization
Initialize the SDK by calling SpeedAssistant.with(context, "YOUR_JWT_LICENSE"). The SDK employs a lightweight manual service locator pattern internally, so you can call with(context) from any Android Activity, Fragment, or Application class without setting up Hilt or Dagger.
import com.vegsahayyak.speedcore.api.SpeedAssistant
import com.vegsahayyak.speedcore.units.SpeedUnit
class App : Application() {
override fun onCreate() {
super.onCreate()
// Initialize the SDK with a predefined or custom driving profile
SpeedAssistant.with(this, "YOUR_JWT_LICENSE")
.setDrivingProfile(DrivingProfile.CarHighway)
}
}TIP
Dynamic Limits: The DrivingProfile automatically handles maximum and minimum speed limits based on vehicle type and road legalities. You can also build a DrivingProfile.CustomProfile if you need manual limits.
🚗 2. Starting & Stopping Tracking
Starting the Background Service
Calling .start() initiates the SpeedometerService foreground service and registers the Fused Location Provider client:
val assistant = SpeedAssistant.with(context, "YOUR_JWT_LICENSE")
// Set the driving profile and launch background tracking
assistant
.setDrivingProfile(DrivingProfile.CarHighway)
.start()Auto-Resuming on Boot / App Restart
To restart tracking only if the driver previously had tracking enabled:
class App : Application() {
override fun onCreate() {
super.onCreate()
// Auto-starts service only if preference state is enabled
val wasRunning = SpeedAssistant.with(this, "YOUR_JWT_LICENSE").startIfEnabled()
}
}Stopping Tracking
To stop background tracking and release GPS hardware immediately:
SpeedAssistant.with(context, "YOUR_JWT_LICENSE").stop()📡 3. Observing Real-Time Telematics
The SDK exposes a Kotlin Coroutines StateFlow<SpeedData>. You can collect this stream in Jetpack Compose or traditional ViewModels.
Sealed Interface: SpeedData
The state stream emits instances of the SpeedData sealed interface:
| Subtype | Description | Key Fields |
|---|---|---|
SpeedChanged | Active valid speed reading | speedMps: Float, isOverLimit: Boolean, isUnderLimit: Boolean, accuracyMeters: Float |
LowAccuracy | GPS fix rejected by precision filter | reason: String |
GPSDisabled | Device location provider is turned off | N/A |
SpeedUnavailable | Tracking active, waiting for satellite fix | N/A |
Disabled | Tracking is stopped | N/A |
Jetpack Compose Example
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import com.vegsahayyak.speedcore.api.SpeedAssistant
import com.vegsahayyak.speedcore.data.location.SpeedData
import com.vegsahayyak.speedui.components.SpeedGauge
@Composable
fun DriverDashboardScreen() {
// Collect SDK flow as Compose state
val speedState by SpeedAssistant.observeFlow().collectAsState(initial = SpeedData.SpeedUnavailable)
when (val state = speedState) {
is SpeedData.SpeedChanged -> {
SpeedGauge(
speed = state.speedMps * 3.6f, // Convert m/s to km/h
warningSpeed = 80f, // Maximum speed limit
minSpeedLimit = 40f, // Minimum highway corridor
isTrackingActive = true,
isOverLimit = state.isOverLimit,
gpsAccuracyMeters = state.accuracyMeters
)
// Contextual alert banners based on dual corridor bounds
if (state.isOverLimit) {
Text("⚠️ Slow Down: Maximum speed limit exceeded (80 km/h)!")
} else if (state.isUnderLimit) {
Text("🐢 Speed Up: Maintain minimum expressway corridor speed (40 km/h)!")
}
}
is SpeedData.LowAccuracy -> {
Text("GPS Signal Weak (±${state.reason}) - Searching satellites...")
}
is SpeedData.GPSDisabled -> {
Text("Please enable device GPS in settings.")
}
is SpeedData.SpeedUnavailable,
is SpeedData.Disabled -> {
SpeedGauge(
speed = 0f,
warningSpeed = 80f,
minSpeedLimit = 40f,
isTrackingActive = false
)
}
}
}ViewModel Example (Coroutines)
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
class TelematicsViewModel : ViewModel() {
val speedState = SpeedAssistant.observeFlow()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = SpeedData.SpeedUnavailable
)
}Traditional XML & ViewBinding Integration
If your application uses traditional Android XML layouts and ViewBinding (instead of full Jetpack Compose), the SDK supports both approaches:
Approach A: Embedding SpeedGauge in XML with ComposeView
You can embed the modern SpeedGauge component directly into any XML layout file using ComposeView:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<!-- Speedometer Gauge embedded inside XML -->
<androidx.compose.ui.platform.ComposeView
android:id="@+id/speedGaugeView"
android:layout_width="match_parent"
android:layout_height="220dp" />
<TextView
android:id="@+id/statusText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:textSize="16sp" />
<Button
android:id="@+id/btnToggleTracking"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Start Tracking" />
</LinearLayout>import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.vegsahayyak.speedcore.api.SpeedAssistant
import com.vegsahayyak.speedcore.data.location.SpeedData
import com.vegsahayyak.speedui.components.SpeedGauge
import com.vegsahayyak.speedui.components.ThemeMode
import com.example.app.databinding.ActivityDriverDashboardBinding
import kotlinx.coroutines.launch
class DriverDashboardActivity : AppCompatActivity() {
private lateinit var binding: ActivityDriverDashboardBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityDriverDashboardBinding.inflate(layoutInflater)
setContentView(binding.root)
val assistant = SpeedAssistant.with(this, "YOUR_JWT_LICENSE")
.setDrivingProfile(DrivingProfile.CarHighway)
binding.btnToggleTracking.setOnClickListener {
assistant.start()
}
// Collect telematics Flow within lifecycleScope
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
SpeedAssistant.observeFlow().collect { state ->
when (state) {
is SpeedData.SpeedChanged -> {
val speedKmh = state.speedMps * 3.6f
binding.statusText.text = "Speed: ${speedKmh.toInt()} km/h"
// Update embedded ComposeView
binding.speedGaugeView.setContent {
SpeedGauge(
speed = speedKmh,
warningSpeed = 80f,
minSpeedLimit = 40f,
isTrackingActive = true,
isOverLimit = state.isOverLimit,
themeMode = ThemeMode.DARK_GRAY
)
}
}
is SpeedData.LowAccuracy -> {
binding.statusText.text = "GPS Signal Weak (±${state.reason})"
}
is SpeedData.GPSDisabled -> {
binding.statusText.text = "GPS Disabled"
}
is SpeedData.SpeedUnavailable,
is SpeedData.Disabled -> {
binding.statusText.text = "Standby"
}
}
}
}
}
}
}Approach B: Pure Headless :speed-core (Zero Compose Dependencies)
If your app does not include Jetpack Compose libraries, you can import only speed-core and bind directly to your existing Android Views:
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
SpeedAssistant.observeFlow().collect { state ->
if (state is SpeedData.SpeedChanged) {
val speedKmh = (state.speedMps * 3.6f).toInt()
binding.speedTextView.text = "$speedKmh km/h"
binding.speedProgressBar.progress = speedKmh
binding.alertIndicator.visibility = if (state.isOverLimit) View.VISIBLE else View.GONE
}
}
}
}🔔 4. Customizing Fullscreen Alarms
When a driver exceeds the speed limit, SpeedometerService fires a high-priority alarm notification with full-screen intent capabilities. You can specify a custom PendingIntent to direct the driver to your app's custom warning screen:
val warningIntent = Intent(context, CustomAlertActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
putExtra("TRIGGERED_AT_SPEED", 95.5f)
}
val pendingIntent = PendingIntent.getActivity(
context,
0,
warningIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
SpeedAssistant.with(context, "YOUR_JWT_LICENSE")
.setWarningPendingIntent(pendingIntent)
.start()🎨 5. Client Branding & Custom Color Schemes
You can dynamically inject custom brand colors to style notifications and speedometer UI elements without subclassing or editing library resources:
import com.vegsahayyak.speedcore.api.SpeedColorScheme
import com.vegsahayyak.speedui.components.SpeedUiColorScheme
import androidx.compose.ui.graphics.Color
// 1. Configure Core notification colors
val brandColors = SpeedColorScheme(
safeColor = 0xFF00E676.toInt(),
warningApproachColor = 0xFFFF9100.toInt(),
overSpeedColor = 0xFFFF1744.toInt(),
underSpeedColor = 0xFF00E5FF.toInt(),
primaryAccentColor = 0xFF6200EE.toInt()
)
SpeedAssistant.with(context, "YOUR_JWT_LICENSE")
.setColorScheme(brandColors)
.start()
// 2. Pass matching Compose colors to UI components
val composeBrandColors = SpeedUiColorScheme(
safeColor = Color(0xFF00E676),
warningApproachColor = Color(0xFFFF9100),
overSpeedColor = Color(0xFFFF1744),
underSpeedColor = Color(0xFF00E5FF),
primaryAccentColor = Color(0xFF6200EE)
)
SpeedGauge(
speed = currentSpeedKmh,
warningSpeed = 80f,
isTrackingActive = true,
colorScheme = composeBrandColors
)🎯 6. Advanced Features: PIP, Voice, Autostart, & HUD
The SDK provides powerful drop-in components to supercharge your telematics app.
A. PIP Widget (Floating Overlay)
The speed-overlay module allows your app to display a floating circular speedometer over third-party navigation apps like Google Maps.
import com.vegsahayyak.speedoverlay.PipWidget
// Inside your Compose screen
PipWidget(
speed = currentSpeedKmh,
unitLabel = "km/h",
isOverLimit = state.isOverLimit
)B. Voice Commands
Integrate hands-free operation using the speed-ui module. The VoiceCommandButton automatically handles audio recording permissions and offline parsing.
import com.vegsahayyak.speedui.components.VoiceCommandButton
VoiceCommandButton(
onCommandParsed = { action ->
when (action) {
is VoiceAction.SetProfile -> assistant.setDrivingProfile(action.profile)
is VoiceAction.StopTracking -> assistant.stop()
}
}
)C. Zero-Touch Autostart
Use the speed-autostart module to completely bypass manual start/stop interactions. This utilizes the Google Play Services hardware sensor hub for extreme battery efficiency.
import com.vegsahayyak.speedautostart.AutoStartManager
// Enable activity recognition trip detection
AutoStartManager.enable(context, onSuccess = {
Log.d("SpeedSDK", "Autostart configured successfully!")
})D. HUD Mode (Windshield Projection)
For night driving, the speed-ui module includes a specialized HudDisplay component that mirrors the UI for reflection on a car windshield.
import com.vegsahayyak.speedui.components.HudDisplay
// Automatically flips the UI horizontally for mirror reflection
HudDisplay(
speed = currentSpeedKmh,
isOverLimit = state.isOverLimit
)📚 Specialized Feature Guides
Explore in-depth documentation for v2.2.0 modules and features:
| Feature Guide | Module | Description |
|---|---|---|
| 🚗 Driving Profiles & Corridors | :speed-core | Pre-configured legal limits (Highway, City, Trucks, Agriculture, Scooters, Bikes, Walking, School zones) and dynamic CustomProfile. |
| 🎙️ Voice Commands & AI NLU | :speed-ui | Hands-free operation with VoiceCommandButton, offline regex, and Google Gemini / LLM NLU parser injection. |
| 🪟 Floating PiP Overlay | :speed-overlay | Picture-in-Picture circular speedometer widget hovering over Google Maps and Waze using SYSTEM_ALERT_WINDOW. |
| 🚗 In-Vehicle Auto-Start | :speed-autostart | Zero-touch journey tracking with Google Play Activity Recognition (IN_VEHICLE) and battery-saving auto-stop. |
| ⚙️ Telematics & Signal Processing | :speed-core | Discrete EMA signal smoothing, GPS confidence gating, Dual Schmitt Trigger Hysteresis, and non-standard vehicle usage. |
| 🔋 Permissions & Battery Optimization | Cross-Module | Android 10-15 background location, battery optimization exemptions, and OEM killer workarounds. |
| 🧪 Testing & QA Guide | Cross-Module | Comprehensive testing workflows with FakeSpeedRepository, MockK, Turbine, and Compose UI tests. |