Skip to main content

Visla GPS β€” Android App

What is Visla GPS?​

Visla GPS is a real-time GPS tracking and device management application for Android. It connects to GPS tracking devices to provide live location monitoring, trip history, geofencing, remote commands, and device sharing β€” all backed by a subscription-based billing model through Google Play.

Who is it for?​

  • Fleet managers β€” monitor vehicle fleets in real time with geofence alerts and trip history
  • Vehicle owners β€” track personal vehicles with theft alerts and location sharing
  • Delivery services β€” coordinate drivers with live map views and command dispatch
  • Families β€” share device locations with family members for peace of mind

Key Features​

FeatureDescription
Live Map TrackingReal-time device positions on Google Maps or Mapbox with WebSocket updates
Device ManagementAdd devices via QR code or manual entry, configure settings, view status
Trip HistoryBrowse past trips with route playback and event timeline
GeofencingCreate, edit, and manage geofence zones with enter/exit notifications
Device SharingShare device access with other users via invite system
Remote CommandsSend commands to devices (engine cut, door lock, etc.) with history tracking
Push NotificationsFirebase FCM-powered alerts for geofence events, device status, and more
Home Screen WidgetGlance-based widget showing device grid with status at a glance
Subscription BillingGoogle Play Billing integration with plan management
Multi-Provider MapsSwitch between Google Maps and Mapbox per user preference
Social LoginGoogle Sign-In and Facebook Login alongside email/password auth
Two-Factor AuthTOTP-based 2FA with QR code setup via ML Kit and ZXing

Tech Stack​

CategoryTechnology
LanguageKotlin 2.3.10
UI FrameworkJetpack Compose (BOM 2026.02.00) + Material 3
DIHilt 2.59.2 (with KSP)
NetworkingRetrofit 3.0.0 / OkHttp 5.3.2 / WebSocket
MapsGoogle Maps Compose 8.1.0 + Mapbox 11.18.1
Push NotificationsFirebase Cloud Messaging (BOM 34.9.0)
Local StorageDataStore Preferences (encrypted via Tink 1.20.0)
Secure StorageEncryptedSharedPreferences (security-crypto 1.1.0)
BillingGoogle Play Billing 8.3.0
NavigationNavigation Compose 2.9.7
WidgetsGlance AppWidget 1.1.1 + WorkManager 2.11.1
Camera / QRCameraX 1.5.3 + ML Kit Barcode 17.3.0 + ZXing 3.5.4
Auth ProvidersGoogle Identity (Credential Manager 1.5.0) + Facebook Login 18.1.3
Lintingktlint 1.5.0, detekt 1.23.8, Android Lint
TestingJUnit 4, Mockito 5.21.0, MockK 1.14.9, Turbine 1.2.1, Compose UI Tests

Build Configuration​

PropertyValue
Android Gradle Plugin9.0.1
Kotlin2.3.10 (via kotlin-gradle-plugin, overriding AGP built-in)
KSP2.3.6
Java Toolchain21
minSdk26 (Android 8.0 Oreo)
targetSdk / compileSdk36
Compose BOM2026.02.00
Configuration CacheEnabled
Core Library Desugaringdesugar_jdk_libs 2.1.5

AGP 9.0 uses built-in Kotlin support (android.builtInKotlin=true in gradle.properties), with an explicit higher Kotlin version applied via the buildscript classpath.

Project Structure​

com.visla.vislagps/
β”œβ”€β”€ MainActivity.kt # Single-activity entry point
β”œβ”€β”€ VislaGPSApp.kt # Application class (Hilt entry point)
β”‚
β”œβ”€β”€ core/ # Core infrastructure
β”‚ β”œβ”€β”€ cache/ # In-memory caching
β”‚ β”œβ”€β”€ config/ # Deep link configuration
β”‚ β”œβ”€β”€ data/ # Core data utilities
β”‚ β”œβ”€β”€ geometry/ # Geo math helpers
β”‚ β”œβ”€β”€ initialization/ # App startup logic
β”‚ β”œβ”€β”€ network/ # Auth interceptor, token management, encrypted storage
β”‚ β”œβ”€β”€ notification/ # Notification channel & display manager
β”‚ β”œβ”€β”€ permissions/ # Runtime permission handling
β”‚ β”œβ”€β”€ service/ # FCM service & token manager
β”‚ └── utils/ # Shared utilities
β”‚
β”œβ”€β”€ data/ # Data layer
β”‚ β”œβ”€β”€ auth/ # Google & Facebook auth managers
β”‚ β”œβ”€β”€ billing/ # Google Play Billing manager
β”‚ β”œβ”€β”€ mappers/ # DTO ↔ entity mappers
β”‚ β”œβ”€β”€ models/ # Data models
β”‚ β”œβ”€β”€ network/ # WebSocket manager
β”‚ β”œβ”€β”€ remote/
β”‚ β”‚ β”œβ”€β”€ api/ # Retrofit API interfaces (Auth, Device, Geofence, etc.)
β”‚ β”‚ └── dto/ # Data transfer objects
β”‚ └── repositories/ # Repository implementations
β”‚
β”œβ”€β”€ domain/ # Domain layer
β”‚ β”œβ”€β”€ entities/ # Business entities (Device, Position, Geofence, User, etc.)
β”‚ β”œβ”€β”€ errors/ # Domain error types
β”‚ β”œβ”€β”€ repositories/ # Repository interfaces
β”‚ β”œβ”€β”€ services/ # Service interfaces
β”‚ β”œβ”€β”€ usecases/ # Use cases organized by feature
β”‚ β”‚ β”œβ”€β”€ auth/ # Authentication flows
β”‚ β”‚ β”œβ”€β”€ billing/ # Subscription management
β”‚ β”‚ β”œβ”€β”€ commands/ # Remote device commands
β”‚ β”‚ β”œβ”€β”€ deeplink/ # Deep link resolution
β”‚ β”‚ β”œβ”€β”€ devices/ # Device CRUD
β”‚ β”‚ β”œβ”€β”€ events/ # Event retrieval
β”‚ β”‚ β”œβ”€β”€ geofences/ # Geofence management
β”‚ β”‚ β”œβ”€β”€ notifications/ # Notification preferences
β”‚ β”‚ β”œβ”€β”€ positions/ # Position history
β”‚ β”‚ β”œβ”€β”€ realtime/ # WebSocket real-time updates
β”‚ β”‚ └── sharing/ # Device sharing & invites
β”‚ └── ValidationConstants.kt # Shared validation rules
β”‚
β”œβ”€β”€ di/ # Hilt dependency injection modules
β”‚ β”œβ”€β”€ AppModule.kt
β”‚ β”œβ”€β”€ AppPreferencesModule.kt
β”‚ β”œβ”€β”€ BaseUrlModule.kt
β”‚ β”œβ”€β”€ BillingModule.kt
β”‚ β”œβ”€β”€ NetworkModule.kt
β”‚ β”œβ”€β”€ RepositoryModule.kt
β”‚ β”œβ”€β”€ ServiceModule.kt
β”‚ └── TokenManagerModule.kt
β”‚
β”œβ”€β”€ ui/ # Presentation layer
β”‚ β”œβ”€β”€ base/ # Base composable patterns
β”‚ β”œβ”€β”€ components/ # Reusable UI components
β”‚ β”‚ β”œβ”€β”€ common/ # Buttons, dialogs, inputs
β”‚ β”‚ β”œβ”€β”€ device/ # Device cards & lists
β”‚ β”‚ β”œβ”€β”€ geofence/ # Geofence editors
β”‚ β”‚ β”œβ”€β”€ map/ # Map overlays
β”‚ β”‚ β”œβ”€β”€ notifications/ # Notification items
β”‚ β”‚ β”œβ”€β”€ settings/ # Settings rows
β”‚ β”‚ └── subscription/ # Billing UI
β”‚ β”œβ”€β”€ formatters/ # Display formatting utilities
β”‚ β”œβ”€β”€ handlers/ # UI event handlers
β”‚ β”œβ”€β”€ maps/ # Google Maps & Mapbox composables
β”‚ β”œβ”€β”€ navigation/ # Navigation graph & bottom bar
β”‚ β”œβ”€β”€ screens/ # Feature screens (one file per screen + ViewModel)
β”‚ β”œβ”€β”€ state/ # UI state holders
β”‚ β”œβ”€β”€ subscription/ # Subscription flow
β”‚ β”œβ”€β”€ theme/ # Material 3 theme (colors, typography, dimensions)
β”‚ β”œβ”€β”€ utils/ # UI utilities
β”‚ └── viewmodels/ # Shared ViewModels
β”‚
└── widget/ # Home screen widget
β”œβ”€β”€ DeviceGridWidget.kt # Glance widget definition
β”œβ”€β”€ WidgetDataStore.kt # Widget data persistence
β”œβ”€β”€ WidgetDevice.kt # Widget device model
β”œβ”€β”€ WidgetGridBuilder.kt # Grid layout builder
└── WidgetSyncWorker.kt # Background sync via WorkManager

Getting Started​

Prerequisites​

  • Android Studio Meerkat (2024.3+) or later with AGP 9.0 support
  • JDK 21 (bundled with Android Studio at /opt/android-studio/jbr)
  • Android SDK with API 36 platform installed
  • Mapbox account for Mapbox SDK downloads token
  • Physical device or emulator (API 26+)

Setup​

  1. Clone the repository and navigate to the Android project:

    cd android
  2. Create local.properties from the example:

    cp local.properties.example local.properties
  3. Fill in required values in local.properties:

    sdk.dir=/path/to/your/Android/Sdk
    GOOGLE_MAPS_API_KEY=your_google_maps_api_key
    MAPBOX_ACCESS_TOKEN=your_mapbox_access_token
    FACEBOOK_APP_ID=your_facebook_app_id
    FACEBOOK_CLIENT_TOKEN=your_facebook_client_token
  4. Set the Mapbox downloads token (required to resolve Mapbox Maven dependencies):

    export MAPBOX_DOWNLOADS_TOKEN=your_mapbox_downloads_token

    Or add to ~/.gradle/gradle.properties:

    MAPBOX_DOWNLOADS_TOKEN=your_mapbox_downloads_token
  5. Build the project:

    make build

Local Development​

For local backend development, uncomment and set the environment URLs in local.properties:

API_BASE_URL=http://10.0.2.2:8080
WS_BASE_URL=ws://10.0.2.2:8080

Note: 10.0.2.2 is the Android emulator alias for the host machine's localhost.

Use make dev to build, install, set up ADB port forwarding, and launch the app in one step.

Makefile Targets​

TargetDescription
make helpShow all available targets with usage examples
make buildBuild debug APK (assembleDebug)
make releaseBuild release AAB (bundleRelease)
make installBuild and install debug APK to connected device
make launchLaunch the app on a connected device
make devFull dev workflow: build β†’ install β†’ port-forward β†’ launch
make uninstallUninstall the app from the device
make test-unitRun all unit tests
make test-unit test=XRun unit tests matching a pattern (e.g., test=DevicesViewModel)
make test-uiRun instrumented UI tests on a connected device/emulator
make lintRun all linters (ktlint + detekt + Android Lint)
make ktlintRun ktlint code style checks
make ktlint-fixRun ktlint with auto-fix
make detektRun detekt static analysis
make lint-androidRun Android Lint
make cleanClean build artifacts
make clean-adbReset ADB server
make clean-allClean build + reset ADB
make logsTail app logs filtered by PID, grepping for visla, Exception, Error
make devicesList connected ADB devices
make connectConnect to a device over WiFi
make emuStart an emulator (EMU=name)
make emulatorsList available AVD emulators
make stop-emuStop the running emulator

Device selection is controlled by the VIA variable:

make dev                   # WiFi (default, DEVICE=android:5555)
make dev VIA=usb # USB-connected device
make dev VIA=emu # Running emulator
make install VIA=usb # Install via USB
make logs VIA=emu # Logs from emulator

Architecture​

The app follows Clean Architecture with three layers:

  • UI β†’ Domain: ViewModels invoke use cases; never touch repositories directly.
  • Domain β†’ Data: Domain defines repository interfaces; data provides implementations.
  • Dependency rule: Inner layers have no knowledge of outer layers. The domain layer has zero Android framework dependencies.

Design Decisions​

Jetpack Compose over XML Views​

The entire UI is built with Compose. It eliminates the fragment lifecycle complexity, reduces boilerplate, and enables a declarative, state-driven UI model that pairs naturally with Kotlin coroutines and flows. The theme system uses Material 3 with custom color, typography, and dimension tokens defined in ui/theme/.

Hilt over Manual DI​

Hilt provides compile-time verified dependency injection with minimal boilerplate. The di/ package contains eight focused modules (NetworkModule, RepositoryModule, BillingModule, etc.) that keep bindings organized by concern. KSP is used instead of kapt for faster annotation processing.

Clean Architecture with Use Cases​

Each user-facing operation is encapsulated in a dedicated use case class under domain/usecases/, organized by feature. This keeps ViewModels thin β€” they orchestrate use cases rather than containing business logic β€” and makes the domain layer independently testable without Android dependencies.

Dual Map Provider (Google Maps + Mapbox)​

The app supports both Google Maps and Mapbox through a MapProviderManager abstraction in the data layer. Map composables in ui/maps/ provide provider-specific implementations (GoogleMapContent, MapboxMapContent) behind a unified MapContent interface. This gives users a choice and reduces vendor lock-in.

Encrypted Token Storage​

Authentication tokens are stored using DataStore Preferences encrypted with Google Tink (EncryptedTokenDataStore). This provides at-rest encryption without the limitations of EncryptedSharedPreferences (which is still available for legacy secure storage). Token refresh is handled transparently by TokenAuthenticator and AuthInterceptor in the OkHttp pipeline.

WebSocket for Real-Time Updates​

Live device positions are delivered over a persistent WebSocket connection managed by WebSocketManager. This avoids polling overhead and provides sub-second position updates on the map. The connection lifecycle is tied to the app's foreground state via lifecycle-process.

Glance Widgets with WorkManager Sync​

The home screen widget uses Jetpack Glance (Compose-based widget framework) rather than legacy RemoteViews. WidgetSyncWorker periodically refreshes device data in the background via WorkManager, keeping the widget up to date without requiring the app to be open.

AGP 9.0 with Built-In Kotlin​

The project uses Android Gradle Plugin 9.0's built-in Kotlin support (android.builtInKotlin=true) with an explicit higher Kotlin version (2.3.10) applied via the buildscript classpath. This simplifies the plugin setup while allowing use of the latest Kotlin features.

Fake Billing in Debug​

Debug builds set USE_FAKE_BILLING=true via BuildConfig, allowing the billing flow to be tested without a Google Play connection. Release builds always use the real billing client.