Building Vehiclo: an AI-powered vehicle management app
The Motivation Behind the Project
Vehiclo turns vehicle ownership into something easier to track by keeping fuel, trips, maintenance, and expenses in one mobile workflow. I wanted the app to be fast enough for everyday use, even when logging from the car or while moving between stops. The idea came from my own frustration with existing vehicle management apps — most of them feel like they were designed for people who enjoy data entry. I wanted something where logging a fuel stop takes five seconds, not fifty. The app needed to be smart enough to detect when I started driving and log the trip automatically, but simple enough that I could also add a manual entry in a few taps. Over time, Vehiclo grew into a comprehensive vehicle management platform with cloud sync, voice input, and AI-powered receipt scanning.
Core Features and Design Goals
- Fuel and trip tracking: a simple way to keep mileage and cost records in one place. Users can log fuel stops with just odometer reading and litres — the app calculates consumption, cost per km, and efficiency trends automatically.
- Automatic drive detection: the app detects when you start driving and logs the trip hands-free. This uses Google Activity Recognition API to detect the IN_VEHICLE activity, then starts GPS tracking in a foreground service. When the drive ends, the trip summary (distance, duration, route polyline) is surfaced for review.
- Maintenance history: a timeline that helps users see what was done and when. Each maintenance entry can include photos, receipts, cost, and service provider details.
- Voice logging: quick capture for moments when typing would slow things down. The app uses ML Kit speech recognition and a custom NLP parser to extract fuel amounts, prices, and odometer readings from natural language in five languages.
- Gig worker support: a layout and workflow that make sense for people whose vehicles are part of their job. The app can track multiple vehicles, separate personal from business trips, and export data for tax purposes.
Deep Dive: How It Works Under the Hood
The most technically complex feature in Vehiclo is the automatic drive detection pipeline. On Android, detecting that the user is driving requires using the Google Activity Recognition Transition API, which fires callbacks when the device transitions between activities like walking, running, and driving. I built this as a custom Expo native module in Kotlin because none of the existing React Native packages provided the level of control I needed. When the module detects an ENTER transition for DetectedActivity.IN_VEHICLE, it starts a foreground service (VehicloTrackerService) that requests high-accuracy GPS updates every 5 seconds with a minimum displacement of 20 metres. Each GPS fix accumulates distance using Location.distanceTo() with a noise filter that ignores jumps smaller than 5 metres or larger than 5 kilometres (which usually indicate GPS drift or teleportation). The route is recorded as a JSON array of lat/lng/timestamp objects stored in SharedPreferences. When the Activity Recognition fires an EXIT transition, the service stops, and the completed trip data is surfaced to the React Native layer for user review. The whole pipeline also survives device reboots — a BootReceiver re-registers the Activity Recognition PendingIntent after the device restarts. Getting this to work reliably across different Android manufacturers was one of the hardest parts of the project, because many OEMs (Xiaomi, Samsung, Huawei) aggressively kill background services to save battery.
Technical Implementation
- Built a fully automatic drive detection pipeline as a custom Expo native module in Kotlin: a BroadcastReceiver listens for Google Activity Recognition ENTER/EXIT transitions on DetectedActivity.IN_VEHICLE, and on ENTER it starts a VehicloTrackerService foreground service that requests high-accuracy GPS updates every 5 seconds (20m minimum displacement). Each GPS fix accumulates distance via Location.distanceTo() with a 5m–5km noise filter, while route points (lat/lng/timestamp) are appended to a JSON array in SharedPreferences. On EXIT, the service stops and the pending trip (distance + polyline) is surfaced to the React Native layer for user review. A BootReceiver re-registers the Activity Recognition PendingIntent after device restarts.
- Built a custom offline-first optimistic sync engine using a React Context-based DataProvider that writes to AsyncStorage immediately and pushes to Supabase in the background, with automatic snake_case/camelCase conversion between the JS and Postgres layers. On boot, the app fires 8 parallel Supabase queries (vehicles, fuel_logs, trip_logs, maintenance, expenses, documents, solo_sessions, vehicle_checks) with a full local-storage fallback for guest/offline mode.
- Built a multi-language voice logger that uses a native ML Kit speech recognition module, parses spoken input through regex-based NLP supporting English, Spanish, French, German, and Greek, and heuristically classifies entries as fuel, trip, or expense — with smart number extraction that distinguishes liters from price per liter (e.g., if one number is <5 and the other >5, it swaps them to the correct semantic slot).
- Designed the data layer with automatic odometer sanitization (Math.round on every read/write) to prevent float drift across trip and fuel logs, and cloud image interception — when a local file:// URI is passed for a vehicle photo or document, it's uploaded to Supabase Storage and the remote URL is persisted instead.
Challenges I Faced Along the Way
The biggest challenge was battery consumption. Running continuous GPS tracking in a foreground service is inherently power-hungry, and I had to find the right balance between accuracy and battery drain. After extensive testing, I settled on 5-second intervals with 20-metre minimum displacement — this provides enough resolution for meaningful trip tracking while keeping battery usage under 3% per hour of driving. Another major challenge was handling the differences between Android manufacturers. Samsung, Xiaomi, and Huawei all have aggressive battery optimisation systems that kill background services. I had to build a comprehensive onboarding flow that guides users through disabling battery optimisation for the app, with manufacturer-specific instructions and deep links to the relevant settings screens.
Technology Choices and Alternatives
I considered using a third-party service like Google Maps Timeline for trip tracking, but it lacks the granularity I needed for fuel-to-trip correlation. I also evaluated existing React Native packages for activity recognition, but they were either outdated or did not support the Transition API (only the older, less reliable polling API). Building the native module from scratch gave me full control over the detection pipeline and allowed me to integrate features like route polyline recording and automatic trip categorisation that would have been impossible with a third-party wrapper.
The Technology Stack
Built with Expo SDK 54, Expo Router, TypeScript, and Supabase. The auto-drive feature is a custom Expo native module (Kotlin) using Google Activity Recognition API, FusedLocationProvider for GPS tracking in a foreground service, and SharedPreferences for trip accumulation. The app also includes a second native module for ML Kit speech recognition. State is managed through React Context with optimistic local-first writes synced to 8 Supabase tables. The architecture separates data persistence, sync logic, and UI rendering into distinct layers.
What I Would Do Differently Next Time
The voice logging feature taught me an important lesson about scope. I initially planned to use a large language model for parsing spoken input, but the latency was unacceptable for a feature that needs to feel instant. The regex-based NLP parser I built instead is much less flexible, but it responds in milliseconds and handles the common cases reliably. If I were to rebuild this, I would start with the simple parser and only add LLM-based parsing as an optional fallback for ambiguous inputs — not as the primary path.
Final Reflections
Vehiclo is the kind of project I enjoy most: practical, specific, and designed around a real routine instead of a generic demo. The most valuable thing I learned from building it is that the best features are often invisible — automatic drive detection, background sync, and odometer sanitisation all work behind the scenes without the user ever thinking about them, and that is exactly the point.