Skip to content

taqisystems/onebusaway-android

Repository files navigation

KelantanBus Android

A real-time transit app for the Kelantan public bus network — native Android, built with Jetpack Compose.

Package: com.taqisystems.bus.android
Min SDK: 24 (Android 7.0) · Target SDK: 36
Build status: CI


Features

Feature Description
Live map Google Map centred on user location; zoom-adaptive stop markers
Stop arrivals Real-time + scheduled arrivals from OneBusAway API with 30 s auto-refresh
Arrival reminders Push reminder N minutes before your ride arrives (OneSignal + sidecar)
Destination alerts GPS foreground service: TTS voice + vibration as bus approaches your stop
Trip planner Origin → Destination routing via OpenTripPlanner
Itinerary view Leg-by-leg breakdown with a Destination Alert FAB
Route timeline Live vehicle position overlaid on stop timeline
Saved stops Persist favourite stops across sessions
Multi-region 11 Malaysian regions selectable at runtime
Localisation English + Bahasa Malaysia UI strings

Tech Stack

Layer Technology
Language Kotlin 2.2.0
UI Jetpack Compose (BOM 2025.04.01), Material 3
Navigation androidx.navigation:navigation-compose 2.9.0
Maps maps-compose 6.4.4 · play-services-maps 19.2.0
Networking OkHttp 4.12.0 · Retrofit 2.11.0 · Gson 2.13.1
Transit data OneBusAway Kotlin SDK 0.1.0-alpha.77
Push notifications OneSignal SDK 5.1.21
Persistence DataStore Preferences 1.1.4
Build AGP 9.0.1 · Gradle 9.1.0

Getting Started

Prerequisites

  • Android Studio Meerkat or later
  • JDK 11+
  • A Google Maps API key (Maps SDK for Android enabled)
  • A OneSignal account and App ID

1. Clone the repository

git clone <repo-url>
cd KelantanBus

2. Create local.properties

local.properties is gitignored. Create it in the project root with the following content:

# ── Android SDK ───────────────────────────────────────────────────────────────
sdk.dir=/path/to/Android/Sdk

# ── Release signing ───────────────────────────────────────────────────────────
STORE_FILE=/absolute/path/to/keystore.jks
STORE_PASSWORD=<keystore password>
KEY_ALIAS=<key alias>
KEY_PASSWORD=<key password>

# ── API keys ──────────────────────────────────────────────────────────────────
ONESIGNAL_APP_ID=<your OneSignal App ID>
GOOGLE_MAPS_API_KEY=<your Google Maps API key>

# ── OBA user-agent ────────────────────────────────────────────────────────────
OBA_USER_AGENT=KelantanBus

# ── Branding (optional — defaults shown) ──────────────────────────────────────
APP_NAME=Kelantan Bus
APP_PRIMARY_COLOR=#C62828
APP_SECONDARY_COLOR=#E53935
APP_TERTIARY_COLOR=#37474F

# ── Service URLs (required — Gradle sync fails if missing) ────────────────────
OBA_BASE_URL=https://api.kelantanbus.com
GEOCODING_BASE_URL=https://geocode.kelantanbus.com/v1
REGIONS_URL=https://cdn.unrealasia.net/onebusaway/regions.json

# ── Social / support URLs (required — Gradle sync fails if missing) ───────────
FACEBOOK_PAGE_URL=https://www.facebook.com/kelantanbus
WHATSAPP_PHONE=60109141767
STATUS_PAGE_URL=https://status.kelantanbus.com

Security: ONESIGNAL_APP_ID and GOOGLE_MAPS_API_KEY are validated at Gradle sync time and injected via BuildConfig / manifestPlaceholders. They are never present in any committed file. If either key was ever accidentally committed, revoke and regenerate it immediately.

3. Build

# Debug APK
./gradlew assembleKelantanbusDebug

# Signed release APK
./gradlew assembleKelantanbusRelease

# Install on connected device
./gradlew installKelantanbusDebug

White-Labelling

The project is structured for multiple transit agency brands using Android product flavors. No Kotlin source files need to be touched to produce a new brand.

What you can change in local.properties

Branding

Key Default Effect
APP_NAME Kelantan Bus Launcher label, About screen, onboarding, notification titles
APP_PRIMARY_COLOR #C62828 Buttons, toolbar, FAB, active nav icon
APP_SECONDARY_COLOR #E53935 Accent chips, live-status badges, active tab
APP_TERTIARY_COLOR #37474F Supporting text, icon tints

Service URLs (required — Gradle sync fails if missing)

Key Effect
OBA_BASE_URL Base URL for all OneBusAway API calls
GEOCODING_BASE_URL Base URL for the Photon geocoding API
REGIONS_URL OBA regions manifest URL

Social / support URLs (required — Gradle sync fails if missing)

Key Effect
FACEBOOK_PAGE_URL Facebook link in the Feedback screen
WHATSAPP_PHONE WhatsApp number (E.164 without +) in the Feedback screen
STATUS_PAGE_URL Service-status link in the More screen

What you override with flavor resource files

Place files under app/src/<flavourName>/res/ — the flavor resource always shadows the equivalent file in app/src/main/res/:

Asset Path within res/
In-app / onboarding logo drawable/logo.png
Launcher icon mipmap-{mdpi,hdpi,xhdpi,xxhdpi,xxxhdpi}/ic_launcher*.png
Adaptive icon foreground mipmap-{density}/ic_launcher_foreground.png
Notification alert sound raw/alert.wav (WAV / OGG / MP3, under 1 MB)

Adding a new brand

  1. Add a flavor block to app/build.gradle.kts:

    create("johor") {
        dimension = "brand"
        applicationId = "com.yourcompany.johorbus"
    }
  2. Create app/src/johor/res/drawable/logo.png and mipmap launcher icons.

  3. Optionally add app/src/johor/res/raw/alert.wav for a custom notification sound.

  4. Set your branding, API keys, and service URLs in local.properties:

    APP_NAME=Johor Bus
    APP_PRIMARY_COLOR=#0057B8
    APP_SECONDARY_COLOR=#003F84
    APP_TERTIARY_COLOR=#1A1A2E
    ONESIGNAL_APP_ID=<johor onesignal app id>
    GOOGLE_MAPS_API_KEY=<johor maps api key>
    OBA_BASE_URL=https://api.johorbus.com
    GEOCODING_BASE_URL=https://geocode.johorbus.com/v1
    REGIONS_URL=https://cdn.example.com/onebusaway/regions.json
    FACEBOOK_PAGE_URL=https://www.facebook.com/johorbus
    WHATSAPP_PHONE=601XXXXXXXXX
    STATUS_PAGE_URL=https://status.johorbus.com
  5. Build:

    ./gradlew assembleJohorRelease

Notification sound note: Channel IDs are scoped to packageName (e.g. com.yourcompany.johorbus_reminders). This prevents Android from locking in a previous brand's sound, since the OS caches a channel's sound on first creation and it cannot be changed programmatically afterward.


Project Structure

KelantanBus/
├── app/
│   ├── build.gradle.kts
│   └── src/
│       ├── kelantanbus/res/        ← KelantanBus brand resources
│       ├── generic/res/           ← White-label template
│       └── main/
│           ├── AndroidManifest.xml
│           ├── res/
│           │   ├── drawable/      ← default logo + icons
│           │   ├── mipmap-*/      ← adaptive launcher icons
│           │   ├── raw/alert.wav  ← default notification sound
│           │   ├── values/        ← English strings + themes
│           │   └── values-ms/     ← Bahasa Malaysia strings
│           └── java/com/taqisystems/bus/android/
│               ├── KelantanBusApplication.kt
│               ├── MainActivity.kt
│               ├── ServiceLocator.kt
│               ├── data/
│               │   ├── AppPreferences.kt
│               │   ├── model/Models.kt
│               │   └── repository/
│               │       ├── ObaRepository.kt
│               │       ├── OtpRepository.kt
│               │       ├── GeocodingRepository.kt
│               │       └── RegionsRepository.kt
│               ├── service/
│               │   └── DestinationAlertService.kt
│               └── ui/
│                   ├── map/VehicleMarkerFactory.kt
│                   ├── navigation/AppNavigation.kt
│                   ├── screens/
│                   ├── theme/
│                   │   ├── BrandConfig.kt  ← runtime brand color parser
│                   │   ├── Color.kt
│                   │   ├── Theme.kt
│                   │   └── Type.kt
│                   └── viewmodel/
├── gradle/libs.versions.toml
├── local.properties               ← gitignored, never committed
├── DOCUMENTATION.md               ← full technical reference
└── README.md                      ← this file

Architecture

The app follows a single-Activity, MVVM + Repository pattern:

Compose UI  ──▶  ViewModel  ──▶  Repository  ──▶  OBA SDK / OkHttp / DataStore
                                                         │
                                                    ServiceLocator (singleton DI)
  • ServiceLocator initialises all repositories once in Application.onCreate and exposes them as singletons.
  • ViewModels hold StateFlows consumed by Composables.
  • Repositories wrap all I/O: OneBusAway SDK, OpenTripPlanner REST, Pelias geocoding, DataStore preferences, and the OBA sidecar.

API Services

Service Base URL Auth
OneBusAway https://api.kelantanbus.com key=TEST query param
OpenTripPlanner https://otp.kelantanbus.com/otp/routers/default/ None
Pelias Geocoding https://geocode.kelantanbus.com/v1/ None
Regions JSON https://cdn.unrealasia.net/onebusaway/regions.json None
OBA Sidecar https://sidecar.kelantanbus.com None

The OBA and OTP base URLs can be overridden at runtime via the Settings screen and are persisted to DataStore.


Permissions

Permission Reason
INTERNET API calls
ACCESS_FINE_LOCATION / ACCESS_COARSE_LOCATION Live map, destination alerts
RECORD_AUDIO Voice search on trip planner
POST_NOTIFICATIONS Arrival reminders, destination alert notifications
FOREGROUND_SERVICE / FOREGROUND_SERVICE_LOCATION GPS tracking while on the bus
VIBRATE Haptic feedback on destination alert

Notifications

Two notification channels are registered on app start, both scoped to packageName to support white-label isolation:

Channel Importance Sound
{pkg}_reminders HIGH res/raw/alert.wav
{pkg}_destination LOW Silent (ongoing foreground service)

Arrival reminders are registered through the sidecar service and delivered via OneSignal. The KelantanBusNotificationExtension intercepts each push, suppresses OneSignal's default display, and re-posts via NotificationCompat to ensure the custom sound plays.


Full Documentation

See DOCUMENTATION.md for the complete technical reference, including:

  • All domain models and repository method signatures
  • AppPreferences DataStore schema
  • Screen-by-screen UI breakdown and ViewModel state descriptions
  • Map marker drawing specifications
  • Known SDK quirks and workarounds
  • Unit test reference

License

Copyright 2026 Taqi Systems

Licensed under the Apache License, Version 2.0. You may not use this project except in compliance with the License. A copy of the License is included in the LICENSE file.

All source files carry the following SPDX identifier:

SPDX-FileCopyrightText: 2026 Taqi Systems
SPDX-License-Identifier: Apache-2.0

About

No description, website, or topics provided.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages