white_label_kit 0.0.5
white_label_kit: ^0.0.5 copied to clipboard
A complete multi-tenant white-label and flavor management toolkit for Flutter apps. Automatically configures Android Gradle and iOS Xcode schemes from a single YAML file.
white_label_kit #
The modern, automated flavor & multi-tenant white-label toolkit for Flutter.
Easily manage multiple branded apps, flavors, and client tenants from a single Flutter codebase. Define all your tenants in white_label.yaml, and let white_label_kit automate Android Gradle flavors, iOS Xcode build schemes, IDE configurations, and compile-time asset isolation.
๐ก Why white_label_kit? #
Managing multiple flavors or white-label client apps in Flutter usually means:
- Hand-editing complex
android/app/build.gradle.ktsproduct flavors. - Manually creating and wiring iOS Xcode build configurations, schemes, and bundle identifiers.
- Risking asset leakage where one tenant's logos or credentials accidentally get bundled into another tenant's app.
- Manually configuring IDE debug and build tasks for every new flavor.
white_label_kit automates all of this:
- ๐ Single Source of Truth: Declare all tenants, bundle IDs, colors, API endpoints, environments, and feature flags in one
white_label.yaml. - ๐ค Native File Automation: Patches Android Gradle and iOS Xcode schemes automatically with
dart run white_label_kit:configure. - ๐จ Icon & Splash Automation: Opt-in per-tenant launcher icon and native splash generation โ including the iOS Xcode wiring step most guides forget (see ยง5).
- ๐ Per-Environment Runtime Config: The same tenant/brand can target
staging/production/anything else via--env, without a second tenant (see ยง6). - ๐ก๏ธ Asset Isolation: Guarantees only the active tenant's assets and configs are compiled into the binary โ never every tenant's data baked into every build.
- โก Interactive CLI Runner: Launch
dart run white_label_kitto easily run or build APK, AAB, and iOS apps without memorizing long commands. - ๐ป 1-Click IDE Configurations: Generates ready-to-use Run/Build configurations for Android Studio, IntelliJ, and VS Code.
- ๐ Type-Safe Runtime API: Access tenant metadata cleanly in your Flutter widgets and services using
WhiteLabelRuntime. - ๐ฉบ
doctor: One command that flags missing assets, stale generated files, and missing peer dependencies (flutter_native_splash) before a build fails on them.
๐ Getting Started #
1. Add Dependency #
Add white_label_kit to your Flutter project's dependencies โ not
dev_dependencies:
flutter pub add white_label_kit
Or manually in pubspec.yaml:
dependencies:
white_label_kit: ^0.0.5
Why a regular dependency, not dev-only:
lib/white_label.g.dart(the generated file โ see step 3) importsWhiteLabelRuntime/WhiteLabelThemefrom this package and is compiled directly into your app, read by your own runtime code (whiteLabelRuntime.environment.apiBaseUrl, theme colors, feature flags, etc.). It is not purely a build-time codegen tool the waybuild_runneris โ putting it underdev_dependencieswould still happen to compile for a leaf app, but is the wrong semantic declaration for a package your shipped binary actually reads from at runtime, and would break if this package's code ever needed to reach another package that's only resolved viadependencies.
2. Initialize Configuration #
Generate a starter white_label.yaml in your project root:
dart run white_label_kit:init
3. Add Your Tenants / Brands #
Add a new brand with a single command:
dart run white_label_kit:add-tenant acme "Acme App" com.example.acme --logo path/to/acme_logo.png
This automatically creates the configuration entry in white_label.yaml
and the asset folder tenants/acme/, copying your real logo in if
--logo was given. Omit --logo and it writes a placeholder
tenants/acme/logo.png instead โ replace that file with the real logo
before shipping (this is the one step white_label_kit genuinely can't
automate for you: it doesn't know what your brand's logo looks like).
4. Configure Android & iOS Native Files #
Sync all native Gradle flavors, Xcode schemes, and IDE run configurations:
dart run white_label_kit:configure
Run dart run white_label_kit:doctor any time to sanity-check the current
setup โ missing assets, a stale lib/white_label.g.dart, a missing
flutter_native_splash dependency if splash_generate is on, etc.
5. Launcher Icons & Native Splash (per tenant) #
Launcher/notification icons and the native splash screen are generated by
two well-established, purpose-built packages โ
icons_launcher and
flutter_native_splash
โ not re-modeled by white_label_kit itself.
icons_launcher is a real dependency of this package, so dart run icons_launcher:create resolves for your app with nothing added to your
own pubspec.yaml. flutter_native_splash can't be a dependency of
this package the same way (it needs the Flutter SDK to resolve, which
this package deliberately doesn't โ see maybeGenerateNativeSplash's doc
comment for the full reasoning). Add flutter_native_splash to your
own app's pubspec.yaml (flutter pub add flutter_native_splash) if
you want the splash generation below โ icon generation needs no such
step.
Opt-in auto-generation (recommended default): declare
features: { icon_generate: true } / { splash_generate: true } for a
tenant in white_label.yaml, and configure/build create
icons_launcher-<id>.yaml / flutter_native_splash-<id>.yaml for you โ
derived from that tenant's assets.icon/assets.logo (icon) or
assets.splash/assets.icon/assets.logo + theme.primary_color (splash)
โ only if the file doesn't already exist. Nothing to hand-author for
the common case, and a file you've already customized is never touched or
overwritten:
tenants:
acme:
features:
icon_generate: true
splash_generate: true
Both flags are off by default โ a tenant that declares neither sees no change in behavior at all.
The auto-created icons_launcher-<id>.yaml includes an adaptive icon
(Android 8.0+/API 26) by default โ adaptive_foreground_image reuses the
tenant's icon/logo, adaptive_background_color uses theme.primary_color
(white if unset). A reasonable automatic default, not a substitute for a
properly-padded, transparent foreground asset โ hand-author the file
with a dedicated foreground image for a polished result.
iOS storyboard registration is automatic โ for the opt-in flag only.
flutter_native_splash:create only writes
ios/Runner/Base.lproj/LaunchScreen<Tenant>.storyboard to disk โ Xcode
never bundles a resource it doesn't know about, so a stock run of that
command alone silently produces a splash screen that never ships. When
splash_generate: true triggers a successful flutter_native_splash:create
run, white_label_kit registers that storyboard into Runner.xcodeproj's
Resources build phase for you right after (idempotent โ safe to re-run,
best-effort โ a missing ruby/xcodeproj gem is reported as a warning in
the output, never a crash).
Manual (full control) โ skip the flags: hand-author
icons_launcher-acme.yaml / flutter_native_splash-acme.yaml yourself
using either package's full config reference (adaptive icon
background/foreground, dark-mode variants, fullscreen, per-platform
overrides, and everything else either supports), then run:
dart run icons_launcher:create --flavor acme
dart run flutter_native_splash:create --flavor acme
Going this route puts you outside the automatic registration above โ
you must still register the storyboard into Xcode yourself
(ios/Runner.xcodeproj's Resources build phase) before it will actually
appear in the built app.
6. Staging / Production (--env) #
environments: + --env switches runtime config (API URL and
whatever else you put in custom:) for the SAME tenant/brand โ it is
not a second tenant. Icon, theme, bundle id, and app name all stay
whatever the tenant already declares; only environment changes. See
Configuration File below for the
YAML shape.
staging/production below are just this README's example names โ the
key under environments: is an arbitrary string you choose, not a
fixed/reserved keyword. Name it whatever matches your own release
process (qa, uat, demo, beta, ...) and pass that exact name to
--env.
dart run white_label_kit:generate --tenant acme --env staging
dart run white_label_kit:configure --tenant acme --env staging
dart run white_label_kit:build --tenant acme --env staging --platform android
dart run white_label_kit:run --tenant acme --env staging
Omit --env anywhere above and the tenant's default environment: block
is used โ fully backward compatible, a white_label.yaml that never
declares environments: needs no change. Passing an --env name the
tenant never declared is a hard error (never a silent fallback to the
default) โ you can't accidentally ship "staging" with production's API
URL baked in.
build/run/configure all (re)generate lib/white_label.g.dart for
whichever tenant/environment they actually resolved to, every time they
run โ there's no separate "don't forget to regenerate" step to remember.
๐ฅ๏ธ Running & Building Your App #
Option A: Interactive Terminal Menu (Recommended) #
Launch the interactive runner:
dart run white_label_kit
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โจ WHITE_LABEL_KIT RUNNER & BUILDER โ
โ Automated Multi-Tenant Flutter CLI & Launcher โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ SELECT TENANT:
[0] Acme App [acme] (Default)
Enter tenant number (default: acme): 0
โก SELECT ACTION:
[1] โถ๏ธ Run in Debug Mode (Simulator / Connected Device)
[2] โก Run in Release Mode (Device)
[3] ๐ Build Release APK (Android)
[4] ๐ฆ Build Release AppBundle / AAB (Google Play Store)
[5] ๐ Build Release iOS (Simulator / Archive)
[6] ๐ง Configure All Tenants (white_label_kit:configure)
[7] โ Add New Tenant (white_label_kit:add-tenant)
[8] โ Remove Tenant (white_label_kit:remove-tenant)
[9] ๐ Analyze & Health Check (Flutter Analyze + Tests)
[0] ๐ช Exit
Option B: Flutter CLI Commands #
You can also run or build directly with standard Flutter commands โ this
bypasses white_label_kit's own build/run (so it does not
regenerate lib/white_label.g.dart for you; run generate/configure
first if you switched tenant or --env):
# Run tenant in debug mode
flutter run --flavor acme --dart-define=TENANT_ID=acme
# Build Android Release APK
flutter build apk --release --flavor acme --dart-define=TENANT_ID=acme
# Build Android Release AppBundle (Google Play)
flutter build appbundle --release --flavor acme --dart-define=TENANT_ID=acme
# Build iOS Release App
flutter build ios --release --flavor acme --dart-define=TENANT_ID=acme
๐ Recommended Folder Structure #
Group tenant-specific logos and platform credentials under the root tenants/ folder:
my_flutter_app/
โโโ white_label.yaml # ๐ Central configuration for all tenants
โโโ tenants/ # ๐ Assets grouped per tenant
โ โโโ acme/
โ โ โโโ logo.png # ๐จ App logo / icon asset
โ โ โโโ firebase/ # ๐ Firebase credentials (optional)
โ โ โโโ google-services.json
โ โ โโโ GoogleService-Info.plist
โ โ
โ โโโ beta/
โ โโโ logo.png
โ โโโ firebase/
โ โโโ google-services.json
โ โโโ GoogleService-Info.plist
โ
โโโ lib/
โ โโโ main.dart
โ โโโ white_label.g.dart # โก Generated typed tenant constants
โโโ pubspec.yaml
โ๏ธ Configuration File (white_label.yaml) #
Define all tenant properties in white_label.yaml:
white_label:
default_tenant: acme
tenants:
acme:
name: "Acme App"
version:
name: "1.0.0"
build_number: 1
android:
application_id: "com.example.acme"
app_name: "Acme App"
# version: # optional โ overrides the shared
# name: "1.0.0" # `version:` above for Android only, if
# build_number: 1 # this platform's release cadence diverges
ios:
bundle_id: "com.example.acme"
app_name: "Acme App"
# version: { ... } # same override shape as android.version
theme:
primary_color: "#1E88E5"
secondary_color: "#FFC107"
# brand_colors: { logo_accent: "#FF0000" } # optional, arbitrary
# feature_colors: { courses: "#00FF00" } # keyed hex-color maps
# section_colors: { header: "#0000FF" } # for apps whose UI
# gradient_colors: { start: "#111111" } # needs more than one
# # primary/secondary
environment: # the DEFAULT โ used whenever
api_base_url: "https://api.example.com" # `--env` isn't passed
# custom: # optional, arbitrary string
# sentry_dsn: "https://..." # key-values for anything else
# cdn_url: "https://cdn.example.com" # the build needs at runtime
environments: # optional โ NAMED overrides of the
staging: # `environment:` block above, selected
api_base_url: "https://staging-api.example.com" # via `--env`
custom: # (see ยง6 above).
sentry_dsn: "https://staging-sentry.example.com"
production:
api_base_url: "https://api.example.com"
# `staging`/`production` are just names picked for this example โ
# any key you write under `environments:` becomes a valid `--env`
# value (e.g. `qa`, `uat`, `demo` all work equally). A tenant that
# never declares `environments:` behaves exactly as before this
# existed โ the whole block is optional. Each named environment is
# an independent override, NOT a patch on top of `environment:` โ
# declare everything that environment needs.
features:
enable_push_notifications: true
enable_downloads: true
# icon_generate: true # see ยง5 above
# splash_generate: true
assets:
logo: "tenants/acme/logo.png"
# icon: "tenants/acme/icon.png" # optional
# splash: "tenants/acme/splash.png" # optional
firebase:
google_services_json: "tenants/acme/firebase/google-services.json"
google_service_info_plist: "tenants/acme/firebase/GoogleService-Info.plist"
๐ฑ Accessing Tenant Data in Flutter (Dart) #
Access your active tenant's branding, API endpoints, and feature flags anywhere in your Dart code:
dart run white_label_kit:generate compiles the current build's tenant
(and, if --env was passed, that specific environment) into
lib/white_label.g.dart as a single whiteLabelRuntime constant (a
WhiteLabelRuntime) โ never a
map of every tenant, so no other tenant's data is ever compiled into a
build that isn't theirs:
import 'package:flutter/material.dart';
import 'white_label.g.dart';
void main() {
print('Tenant ID: ${whiteLabelRuntime.tenantId}');
print('App Name: ${whiteLabelRuntime.tenantName}');
print('Environment: $whiteLabelEnvironmentName'); // "" if --env wasn't used
print('API URL: ${whiteLabelRuntime.environment.apiBaseUrl}');
print('Sentry DSN: ${whiteLabelRuntime.environment.custom['sentry_dsn']}');
print('Primary Color: ${whiteLabelRuntime.theme.primaryColorHex}');
final hasPush = whiteLabelRuntime.isFeatureEnabled('enable_push_notifications');
print('Push Notifications: $hasPush');
runApp(const MyApp());
}
๐งฉ Optional: build_runner Integration #
You do not need build_runner for anything above โ generate/
configure/build/run are plain, direct CLI commands, the same shape
as flutter_native_splash:create. If your app already runs dart run build_runner build for freezed/json_serializable/
injectable_generator and you'd like that same command to also
regenerate lib/white_label.g.dart, this package ships an optional
builder for it (lib/builder.dart) โ but it is not enabled
automatically, and needs two things added to your own project's
build.yaml before it does anything:
# your app's build.yaml
targets:
$default:
sources:
- white_label.yaml # lives at the project root, outside build_runner's default input set
- lib/** # default lib/** scan โ add it explicitly
builders:
white_label_kit:white_label_generator:
enabled: true # NOT auto-applied โ must opt in explicitly
Why this isn't auto-applied: without the sources: override above,
build_runner would activate the builder but it could never actually find
its white_label.yaml input โ while still treating
lib/white_label.g.dart as an output it owns, and deleting it on the next
build_runner build (the file generate/configure had already written
correctly gets wiped with no warning). If you don't need build_runner to
regenerate this file, don't add the build.yaml block above โ generate/
configure are unaffected either way.
๐ CLI Commands Reference #
| Command | Description |
|---|---|
dart run white_label_kit |
Opens the interactive terminal runner & builder menu |
dart run white_label_kit:init [--example] [--force] [--path <dir>] |
Creates a starter white_label.yaml file |
dart run white_label_kit:add-tenant <id> "<Name>" <bundleId> [--logo <path>] [--default] |
Adds a new tenant and creates its asset directory |
dart run white_label_kit:update-tenant <id> [options] |
Updates tenant configuration fields |
dart run white_label_kit:remove-tenant <id> [--keep-assets] |
Removes the tenant's entry from white_label.yaml, deletes its tenants/<id>/ asset folder (unless --keep-assets), and cleans up its generated Android Gradle flavor, iOS Xcode build configs/scheme, and IDE run configurations |
dart run white_label_kit:generate [--tenant <id>] [--env <name>] [--config <path>] |
(Re)generates lib/white_label.g.dart for one tenant/environment |
dart run white_label_kit:configure [--tenant <id>] [--env <name>] [--platform android|ios|all] [--dry-run] [--skip-generate] |
Patches Android Gradle, iOS Xcode, IDE configs, icon/splash (if opted in), and regenerates lib/white_label.g.dart |
dart run white_label_kit:build [--tenant <id>] [--env <name>] [--platform android|android-aab|ios|all] [--mode debug|release] [--dry-run] [--clean] [--stage-only] |
Stages tenant assets, regenerates lib/white_label.g.dart, and invokes the real flutter build. --mode release always adds --obfuscate --split-debug-info=build/outputs/symbols/<tenant>/<platform> (see Flutter's obfuscation guide) โ not optional, so a release build can't accidentally ship un-obfuscated. |
dart run white_label_kit:run [--tenant <id>] [--env <name>] |
Stages tenant assets and regenerates lib/white_label.g.dart for a debug run |
dart run white_label_kit:validate |
Validates white_label.yaml syntax and asset paths |
dart run white_label_kit:list |
Lists all declared tenants and the default tenant |
dart run white_label_kit:doctor [id] [--all] [--json] [--strict] |
Performs a multi-tenant health check |
๐ Security #
white_label.yaml and everything it generates (lib/white_label.g.dart,
native Gradle/Xcode config) end up baked into the built binary โ the
same way any other compiled Flutter asset does. Anyone who unzips a
shipped APK/IPA can read whiteLabelRuntime's data, including everything
under environment.custom/environments.*.custom.
Never put in white_label.yaml: signing keys/certificates, private
API secrets, database credentials, or anything else that would matter if
extracted from the built app. environment/environment.custom is for
public runtime config only (a base URL, a public DSN meant to be
client-visible, a CDN URL) โ not a place to smuggle a secret in because it
was convenient. Firebase's google-services.json/
GoogleService-Info.plist (via firebase:) are the one exception this
package handles directly, and only because Firebase itself designs those
files to ship inside the client app.
๐ซ What this does NOT do (yet) #
Deliberate scope boundaries, not oversights โ flagged here instead of silently discovered later:
- No per-environment theme/icon/splash.
environments:/--env(ยง6) only ever changesenvironment(API URL +custom). Staging and production of the same tenant are expected to look identical; there is noenvironments.staging.themeor similar. If you need visually distinct staging builds, that's a real gap today, not a documented design choice โ open an issue rather than hand-rolling around it. environment/environments.*.customare flat string maps, not arbitrary JSON. Same deliberate constraint asfeatures(bool-only) โ richer/nested structured runtime content isn't modeled here.ASSETCATALOG_COMPILER_APPICON_NAMEandLAUNCH_SCREEN_STORYBOARD_NAME(the two remaining iOS Xcode keysicons_launcher/flutter_native_splashthemselves don't manage, beyond whatgenerateIosConfigsets) are not touched by this package at all โ seemaybeGenerateNativeSplash's storyboard-registration note in ยง5 for the one exception (the storyboard file reference).- No CI/CD orchestration. This package configures native files and generates Dart code; it does not run or generate pipeline definitions (Bitbucket/GitHub Actions/etc.) โ wire its CLI commands into whatever CI you already run.
๐ค Contributing #
Contributions, issues, and feature requests are welcome! Feel free to check the issues page.
๐ License #
This project is licensed under the MIT License โ see the LICENSE file for details.