Rebranding OneBusAway Android for Your City
September 6, 2026 · View on GitHub
There are two ways to deploy OneBusAway Android in your city:
- Join the OneBusAway multi-region project - The easiest way to get started - simply set up your own OneBusAway server with your own transit data, and get added to all the OneBusAway apps! See this page for details.
- Deploy a rebranded version of OneBusAway Android as your own app on Google Play - Requires a bit more maintenance, but it allows you to set up your own app on Google Play based on the OneBusAway Android source code, and with your brand name and colors. This page discusses this option in detail.
Rebranding Using Gradle Build Flavors
We use Gradle build flavors to enable a number of different build variants of OneBusAway Android.
We have one Gradle "platform" flavor dimension:
- google = Normal Google Play release
...and several Gradle "brand" flavor dimensions:
- oba = Original OneBusAway brand
- agencyX = A sample rebranded version of OneBusAway for a fictitious "Agency X"
- agencyY = A sample rebranded version of OneBusAway for a fictitious "Agency Y"
- kiedybus = KiedyBus, a Polish transit app
Here's where you can download three of these brands on Google Play (KiedyBus is maintained by a third party and isn't listed here):
And here are screenshots of those same three brands:
Each brand is deployed as an independent app on Google Play (using the google platform flavor).
To build a variant, you need to combine the platform flavor with the brand flavor. For example, the original OneBusAway brand for the Google platform can be built with:
gradlew installObaGoogleDebug
Creating a New Brand
Brand flavors are defined in separate files in the onebusaway-android/flavors/ directory. This keeps the main build.gradle.kts clean and makes it easy to add new brands.
First, we recommend that you review the sample brands (agencyX, agencyY, kiedybus) to see how brands are implemented.
Here are the high-level steps to add a new brand, for a new brand name newBrandName:
- Create a new flavor configuration file
onebusaway-android/flavors/newBrandName.gradle - Create a new folder
src/newBrandName/reswith the appropriate resource files (or copy from one of the samples) - Edit resource files in
src/newBrandName/ressubfolders with your brand information - Add your own launcher icons for the
res/mipmap-*folders - Configure Google Maps API key for your package name
- Configure Firebase for analytics and crash reporting (optional but recommended)
Step 1: Create Flavor Configuration
Copy an existing flavor file as a template:
cp onebusaway-android/flavors/agencyX.gradle onebusaway-android/flavors/newBrandName.gradle
Edit the file and update:
android.productFlavors {
newBrandName {
dimension "brand"
applicationId "com.newbrandname.android" // Your unique package name
manifestPlaceholders = [databaseAuthority: applicationId.toString() + '.provider',
deepLinkScheme: "newbrandname"] // Optional: your own URL scheme
buildConfigField "int", "ARRIVAL_INFO_STYLE", "0"
buildConfigField "boolean", "USE_FIXED_REGION", "false" // or "true" for single-region
// ... other configuration options
}
}
See onebusaway-android/flavors/README.md for the complete list of configuration options.
Step 2: Create Resource Directory
Create minimal resources in src/newBrandName/res/:
src/newBrandName/
└── res/
├── values/
│ ├── strings.xml # App name (required)
│ ├── colors.xml # Theme colors (required)
│ └── do_not_translate.xml # API keys, URLs (required)
└── mipmap-*/
│ ├── ic_launcher.png # App icon (all densities)
│ └── ic_launcher_round.png
└── values-night/
└── colors.xml # Dark Theme colors (required)
Step 3: Configure Resources
strings.xml
Thanks to the placeholder system, you typically only need to override app_name:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">New Brand Name</string>
</resources>
All other branded strings use %1$s placeholders that automatically substitute your app name.
colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="brand_color">#YOUR_PRIMARY_COLOR</color>
<color name="brand_color_dark">#YOUR_DARK_COLOR</color>
<color name="theme_muted">#YOUR_MUTED_COLOR</color>
<color name="theme_accent">#YOUR_ACCENT_COLOR</color>
<!-- Primary color with a "df" alpha prefix (~87% opaque) -->
<color name="tutorial_background">#dfYOUR_PRIMARY_COLOR</color>
<color name="ic_launcher_background">#YOUR_PRIMARY_COLOR</color>
</resources>
Do not override the stop_info_* colors. They encode schedule deviation (early / on time /
late / no real-time), which is semantic rather than brand identity, and they are intentionally
independent of brand_color so a brand hue can never collide two states into the same color.
To override material 3 colors, please checkout the theme builder.
do_not_translate.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="apiv2_key">YOUR_GOOGLE_MAPS_API_KEY</string>
<!-- Only if using custom regions API: -->
<string name="regions_api_url">https://your-domain.com/regions.json</string>
</resources>
Step 4: Generate Launcher Icons
Use one of these tools to generate icons for all densities:
- Android Asset Studio
- Image Asset Studio in Android Studio
Required sizes:
- mdpi: 48x48
- hdpi: 72x72
- xhdpi: 96x96
- xxhdpi: 144x144
- xxxhdpi: 192x192
Step 5: Configure Google Maps API
Google Maps requires an API key that is restricted to your app's package name.
- Go to the Google Cloud Console
- Create a new project or select an existing one
- Enable the Maps SDK for Android API
- Go to Credentials and create an API key
- Restrict the key:
- Click on the API key to edit it
- Under "Application restrictions", select Android apps
- Add your package name (e.g.,
com.newbrandname.android) - Add your app's SHA-1 fingerprint:
# For debug builds: keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android # For release builds, use your release keystore
- Copy the API key to
src/newBrandName/res/values/do_not_translate.xml
Important: You need separate SHA-1 fingerprints for debug and release builds. For production, add both fingerprints to your API key restrictions.
See Google's documentation for more details.
Step 6: Configure Firebase (Optional but Recommended)
Firebase provides analytics and crash reporting. To set up Firebase for your brand:
- Go to the Firebase Console
- Create a new project or select an existing one
- Click Add app and select Android
- Enter your package name (e.g.,
com.newbrandname.android) - Download the
google-services.jsonfile - Important: Don't replace the existing
google-services.json. Instead, add your app's client configuration to the existing file.
Adding Your App to google-services.json
Open onebusaway-android/google-services.json and append the following object to the existing client array, leaving the top-level project_info and the existing client entries untouched:
{
"client_info": {
"mobilesdk_app_id": "1:YOUR_PROJECT_NUMBER:android:YOUR_APP_ID",
"android_client_info": {
"package_name": "com.newbrandname.android"
}
},
"oauth_client": [
{
"client_id": "YOUR_CLIENT_ID.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "YOUR_FIREBASE_API_KEY"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "YOUR_CLIENT_ID.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
You can find these values in the google-services.json file you downloaded from Firebase Console.
Note: If you don't configure Firebase, the app will still build and run, but you won't have analytics or crash reporting for your brand.
Step 7: Build and Test
./gradlew assembleNewBrandNameGoogleDebug
./gradlew installNewBrandNameGoogleDebug
Simplified String Localization with Placeholders
The app uses a placeholder system for branded strings, which significantly reduces the work needed to support multiple languages in your rebranded app.
How it works: Strings that mention the app name use %1$s as a placeholder, which is automatically replaced with your app_name at runtime. For example:
<!-- In src/main/res/values/strings.xml -->
<string name="tutorial_welcome_title">Welcome to %1$s!</string>
<string name="bad_gateway_error">%1$s\'s servers are overloaded. Please try again.</string>
What this means for you: You only need to override app_name in each language your brand supports. All other branded strings will automatically use your app name.
<!-- src/newBrandName/res/values/strings.xml -->
<resources>
<string name="app_name">New Brand Name</string>
</resources>
<!-- src/newBrandName/res/values-es/strings.xml -->
<resources>
<string name="app_name">New Brand Name</string>
</resources>
Optional overrides: You may still need to override specific strings if your agency has different UI elements. For example, if your agency uses red stop markers instead of green:
<!-- src/newBrandName/res/values/strings.xml -->
<resources>
<string name="app_name">New Brand Name</string>
<!-- Override because our stop dots are red, not green -->
<string name="tutorial_welcome_text">Tap on a stop (red dot on the map) to see arrival times.</string>
</resources>
See the agencyX, agencyY, and kiedybus sample flavors for working examples of this pattern.
Configuration Options
We provide configuration options in the flavor .gradle files that allow you to choose default behaviors for your brand.
Arrival display
Riders choose their default arrivals display in Settings and can switch between Time (one departure per row) and Route (grouped departures) in an open arrivals drawer. The initial fallback is Route. The retired ARRIVAL_INFO_STYLE build option no longer controls this presentation.
Arrival display default in Settings
The migration picker is offered only when the recorded previous release is 26.1.0 or earlier (published version codes 1–154) and no display default has been chosen. Fresh installs, missing version history, and upgrades from later releases skip it. Eligibility is recorded before release notes update the version marker, so dismissing the picker preserves the offer for the next launch.
Fixed vs. Multi-region
USE_FIXED_REGION - Valid values are true and false:
false- The app works across various regions defined in the Regions API (recommended for most brands)true- The app is fixed to the region information provided in the flavor configuration
When USE_FIXED_REGION is true, FIXED_REGION_NAME is required - it is the one
FIXED_REGION_* field the app cannot start without, and leaving it null (the value every
multi-region flavor uses) crashes on the first launch. Every other FIXED_REGION_* field is
optional in the sense that it will not crash: FIXED_REGION_OBA_BASE_URL in particular is
silently accepted as null, which builds and installs fine and then simply cannot reach a
server — so set it too, and set the bounds, or the fixed region will not work.
When USE_FIXED_REGION is false, the FIXED_REGION_* fields are unread; leave them null as
agencyX.gradle does.
Geocoding Provider
USE_PELIAS_GEOCODING - Controls which service is used for searching origins and destinations in trip planning:
true- Uses the Pelias geocoder (configured for geocode.earth by default). Requires a Pelias API key ingradle.properties:Pelias_newBrandName=YOUR_API_KEYfalse- Uses the Google Places SDK. Requires a Google Maps Platform billing account.
Examples
See the following flavor files for complete examples:
agencyX.gradle- Multi-region brandagencyY.gradle- Fixed-region brandkiedybus.gradle- Multi-region brand with custom regions API
Acknowledgements
When launching a rebranded version of OneBusAway, acknowledging that your app is based on the hard work of those contributing to the OneBusAway project is certainly appreciated. However, please do not imply that the OneBusAway project or its contributors endorse the rebranded app, and please do not use the OneBusAway logo or color scheme in your rebranded app.