Developing White-Label Mobile App
White-label mobile app is single codebase compiling into multiple independent products for different clients, brands, or markets. Each instance has own Bundle ID, icon, color scheme, feature set, and sometimes own backend. Developers work with one repository.
Complexity isn't the idea itself—it's laying architecture day one to avoid spaghetti from if (tenantId == "brand_a") throughout codebase in six months.
Architectural Solutions
Product Flavors (Android) and Schemes/Targets (iOS)
Platforms' basic mechanism—Build Flavors on Android and Xcodeconfig/Targets on iOS—allows changing resources, string constants, and compiler flags without code changes.
Android: product flavors
// app/build.gradle.kts
android {
flavorDimensions += "tenant"
productFlavors {
create("brandA") {
dimension = "tenant"
applicationId = "com.brandA.app"
resValue("string", "app_name", "Brand A")
buildConfigField("String", "API_BASE_URL", "\"https://api.brand-a.com\"")
buildConfigField("String", "TENANT_ID", "\"brand_a\"")
}
}
}
Resources (icons, colors, strings) override via directories:
app/src/brandA/res/drawable/ic_launcher.png
app/src/main/res/... ← shared resources
iOS: Xcodeconfig + Multiple Targets
Each client—separate Target sharing code with main target:
MyApp.xcodeproj
├── Targets
│ ├── BrandA ← Bundle ID: com.brandA.app
│ └── Shared Code ← common Swift Package
├── BrandA/
│ ├── Assets.xcassets ← brand icons, colors
│ └── Config.xcconfig ← API URL, feature flags
Feature Flags per Tenant
Different clients often have different feature sets. Flags in xcconfig/BuildConfig determine compilation:
if (BuildConfig.FEATURE_PREMIUM_ENABLED) {
setupPremiumFeatures()
}
For dynamic flags (changeable without recompile)—Firebase Remote Config per tenant or shared project with tenant-specific params.
Theming Engine
Colors, fonts, sizes—load from Theme config, not hardcode:
data class TenantTheme(
val primaryColor: Color,
val fontFamily: String,
val logoResId: Int
)
object ThemeProvider {
fun getTheme(tenantId: String): TenantTheme = when (tenantId) {
"brand_a" -> TenantTheme(
primaryColor = Color(0xFF1A73E8),
fontFamily = "Roboto",
logoResId = R.drawable.logo_brand_a
)
else -> defaultTheme
}
}
CI/CD for Multiple Artifacts
Each main push should build all tenant builds. On GitHub Actions:
strategy:
matrix:
flavor: [brandA, brandB]
steps:
- name: Build APK for ${{ matrix.flavor }}
run: ./gradlew assemble${{ matrix.flavor }}Release
Each flavor deploys to separate Play Store account or single account with different Package Names.
Repository Organization
repo/
├── app/ ← Android app module
│ └── src/
│ ├── main/ ← shared code
│ ├── brandA/ ← Brand A resources
│ └── brandB/ ← Brand B resources
├── core/ ← shared business logic
├── features/ ← feature modules
└── tenants/
├── brand-a.properties
└── brand-b.properties
Monorepo with clear boundaries. No tenant-specific file should reach main/ or core/.
Common White-Label Mistakes
Tenant logic in business code. if (tenantId == "brand_a") showSpecialButton() in ViewModel—chaos path. Correct: tenant-specific behavior via DI or config object injected externally.
Shared strings with hardcoded brands. strings.xml with "Welcome to Brand A" in common directory. Brand names only in tenant directory.
Single Firebase project for all tenants. Firebase Crashlytics and Analytics confuse crashes and events. Each tenant needs own google-services.json.
No tests per flavor. Unit tests pass, but specific flavor doesn't compile—flavor overrode resource test uses. Run tests for each flavor in CI.
Process
Audit and Design—analyze all tenant requirements: shared functionality, differences, planned client count. Choose strategy.
Base Architecture—setup flavors/targets, ThemeProvider, FeatureFlags, CI pipeline.
Module Development—feature modules with dependency inversion for any tenant to include/exclude.
New Tenant Onboarding—config template + checklist: icons, colors, API URL, Firebase project, app accounts.
Timeline
MVP white-label app with 2–3 tenants on one platform—8–14 weeks depending on feature complexity. Cross-platform on Flutter/React Native with 5+ tenants and full CI/CD—16–24 weeks. Cost calculated individually.







