Разработка White-Label мобильного приложения
White-label мобильное приложение — это единая кодовая база, которая компилируется в несколько независимых продуктов для разных клиентов, брендов или рынков. Каждый экземпляр имеет свой Bundle ID, иконку, цветовую схему, набор функций и иногда собственный backend. При этом разработчики работают с одним репозиторием.
Сложность не в самой идее — а в том, чтобы с первого дня заложить архитектуру, которая не превратится в спагетти из if (tenantId == "brand_a") через полгода.
Архитектурные решения
Product Flavors (Android) и Schemes/Targets (iOS)
Базовый механизм платформ — Build Flavors на Android и Xcodeconfig/Targets на iOS — позволяет менять ресурсы, строковые константы и флаги компилятора без изменения кода.
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\"")
}
create("brandB") {
dimension = "tenant"
applicationId = "com.brandB.app"
resValue("string", "app_name", "Brand B")
buildConfigField("String", "API_BASE_URL", "\"https://api.brand-b.com\"")
buildConfigField("String", "TENANT_ID", "\"brand_b\"")
}
}
}
Ресурсы (иконки, цвета, строки) переопределяются через директории:
app/src/brandA/res/drawable/ic_launcher.png
app/src/brandB/res/drawable/ic_launcher.png
app/src/main/res/... ← общие ресурсы
iOS: Xcodeconfig + Multiple Targets
Каждый клиент — отдельный Target в Xcode-проекте, разделяющий код с основным таргетом:
MyApp.xcodeproj
├── Targets
│ ├── BrandA ← Bundle ID: com.brandA.app
│ ├── BrandB ← Bundle ID: com.brandB.app
│ └── Shared Code ← общий Swift Package
├── BrandA/
│ ├── Assets.xcassets ← иконки, цвета бренда
│ └── Config.xcconfig ← API URL, feature flags
└── BrandB/
├── Assets.xcassets
└── Config.xcconfig
Config.xcconfig:
API_BASE_URL = https://api.brand-b.com
TENANT_ID = brand_b
FEATURE_PREMIUM_ENABLED = YES
FEATURE_CHAT_ENABLED = NO
Значения из xcconfig читаются в Info.plist и далее в коде:
let apiURL = Bundle.main.infoDictionary?["API_BASE_URL"] as? String ?? ""
Feature Flags per Tenant
Разные клиенты часто имеют разный набор функций. Флаги, зашитые в xcconfig/BuildConfig, определяют, что компилируется:
// Условная компиляция через BuildConfig
if (BuildConfig.FEATURE_PREMIUM_ENABLED) {
setupPremiumFeatures()
}
Для динамических флагов (изменяемых без перекомпиляции) — Firebase Remote Config с проектом per tenant или единый проект с tenant-specific параметрами.
Theming Engine
Цвета, шрифты, размеры отступов — не хардкодим в код, а загружаем из Theme-конфига:
// Android: AppTheme через theme attributes
data class TenantTheme(
val primaryColor: Color,
val accentColor: Color,
val fontFamily: String,
val cornerRadius: Float,
val logoResId: Int
)
object ThemeProvider {
fun getTheme(tenantId: String): TenantTheme = when (tenantId) {
"brand_a" -> TenantTheme(
primaryColor = Color(0xFF1A73E8),
accentColor = Color(0xFFFB8C00),
fontFamily = "Roboto",
cornerRadius = 8f,
logoResId = R.drawable.logo_brand_a
)
"brand_b" -> TenantTheme(/* ... */)
else -> defaultTheme
}
}
Для React Native и Flutter архитектура аналогична, но через Theme Context (RN) или ThemeData (Flutter).
CI/CD для множества артефактов
Каждый push в main должен собирать все tenant-сборки. На GitHub Actions:
strategy:
matrix:
flavor: [brandA, brandB, brandC]
steps:
- name: Build APK for ${{ matrix.flavor }}
run: ./gradlew assemble${{ matrix.flavor }}Release
- name: Upload to Play Store
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJson: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: com.${{ matrix.flavor }}.app
releaseFiles: app/build/outputs/apk/${{ matrix.flavor }}/release/*.apk
Каждый flavor деплоится в отдельный Play Store аккаунт или в один аккаунт с разными Package Names.
Организация репозитория
repo/
├── app/ ← Android app module
│ └── src/
│ ├── main/ ← общий код
│ ├── brandA/ ← ресурсы Brand A
│ └── brandB/ ← ресурсы Brand B
├── core/ ← shared business logic
├── features/ ← feature modules
│ ├── checkout/
│ ├── profile/
│ └── chat/ ← включается через feature flag
└── tenants/
├── brand-a.properties
└── brand-b.properties
Монорепо с чёткими границами между общим кодом и tenant-специфичным. Ни один tenant-специфичный файл не должен попасть в main/ или core/.
Типичные ошибки при разработке white-label
Tenant logic в бизнес-коде. if (tenantId == "brand_a") showSpecialButton() в ViewModel — это путь к хаосу. Правильно: tenant-специфичное поведение через DI или конфигурационный объект, который инжектится извне.
Shared strings с захардкоженными брендами. strings.xml с текстом «Добро пожаловать в Brand A» в common-директории. Всё что содержит имя бренда — только в tenant-директории.
Единый firebase project для всех tenants. Firebase Crashlytics и Analytics путают крэши и события разных клиентов. У каждого tenant — свой google-services.json / GoogleService-Info.plist.
Отсутствие автотестов для каждого flavor. Unit-тесты проходят, но в конкретном flavor не компилируется — потому что flavor переопределил ресурс, который тест использует по умолчанию. Тесты гоняем для каждого flavor в CI.
Процесс работы
Аудит и проектирование — анализ требований всех tenants: общий функционал, различия, планируемое количество клиентов. Выбор стратегии: flavors/targets или runtime-конфигурация.
Базовая архитектура — настройка flavors/targets, ThemeProvider, FeatureFlags, CI pipeline.
Разработка по модулям — feature modules с инверсией зависимостей, чтобы tenant мог включить или исключить любой модуль.
Onboarding нового tenant — шаблон конфигурационных файлов + чеклист: icons, colors, API URL, Firebase project, App Store/Play Store account.
Ориентиры по срокам
MVP white-label приложения с 2–3 tenants на одной платформе (iOS или Android) — 8–14 недель в зависимости от сложности функционала. Кроссплатформенная реализация на Flutter или React Native с поддержкой 5+ tenants и полным CI/CD — 16–24 недели. Стоимость рассчитывается индивидуально после анализа требований.







