How I Created Multiple Applications with a Single Codebase Using Flutter Flavors ( White-label Applications)
White-label applications are an efficient way to develop multiple branded versions of an app from a single codebase. Recently, I worked on creating a white-label application using Flutter and its powerful Flavors feature. This allowed me to manage different versions of the app for various markets, each with its own branding, assets, and configurations. Here, I’ll walk you through the process and the steps I followed to achieve this.
Understanding Flutter Flavors
Flutter flavors allow you to build different versions of your app from a single codebase. Each flavor can have its own unique configuration, such as different application IDs, resources, and build settings, making it ideal for white-label applications.
Steps to Create a White-Label Application Using Flutter Flavors
1. Setting Up the Project Structure
The first step was to organize the project structure to accommodate the different flavors. I created specific directories for each flavor’s resources (e.g., logos, icons) in the android/app/src directory:
android/app/src/brandA/
android/app/src/brandB/
android/app/src/brandC/
Each directory contains its own res/ folder with flavor-specific resources.
2. Modifying the build.gradle File
I updated the build.gradle file to include the necessary plugins and configurations:
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
android {
namespace "brand.app.id.android"
compileSdk flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
defaultConfig {
applicationId "brand.app.id.android"
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
multiDexEnabled true
}
signingConfigs {
release {
// Signing config for release builds
}
brandA {
// Signing config for Brand A version
}
brandB {
// Signing config for Brand B version
}
brandC {
// Signing config for Brand C version
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
flavorDimensions "default"
productFlavors {
brandA {
dimension "default"
applicationId "brand.app.id.android"
resValue "string", "app_name", "Brand A"
signingConfig signingConfigs.brandA
}
brandB {
dimension "default"
applicationIdSuffix ".brandB"
resValue "string", "app_name", "Brand B"
signingConfig signingConfigs.brandB
}
brandC {
dimension "default"
applicationIdSuffix ".brandC"
resValue "string", "app_name", "Brand C"
signingConfig signingConfigs.brandC
}
}
}
This setup allowed me to define separate product flavors for Brand A, Brand B, and Brand C, each with its unique application ID and signing configuration.
3. Creating a Base Model for Flavors
I created a BaseBrandModel to handle flavor-specific configurations:
abstract class BaseBrandModel {
String get appName;
String get imageAssetPath;
String get logo;
String get primaryColor;
Color get color;
String get languageCode;
String get fontFamily;
AppStrings get appStrings;
TextDirection get textDirection;
Set<String> get subscriptionsIds;
FirebaseOptions get firebaseOptions;
String get appPackage;
static BaseBrandModel? _instance;
static BaseBrandModel get instance {
if (_instance == null) {
throw Exception("BrandModel not initialized. Call initialize() first.");
}
return _instance!;
}
static void initialize(BaseBrandModel model) {
_instance = model;
}
}
This model provides an interface for flavor-specific details, such as app name, primary color, language code, and Firebase options.
Recommended by LinkedIn
4. Initializing the App Based on Flavor
I initialized the app using the appropriate flavor model:
const String? appFlavor = String.fromEnvironment('FLUTTER_APP_FLAVOR');
switch (appFlavor) {
case 'brandA':
BaseBrandModel.initialize(BrandAModel());
break;
case 'brandB':
BaseBrandModel.initialize(BrandBModel());
break;
case 'brandC':
BaseBrandModel.initialize(BrandCModel());
break;
default:
BaseBrandModel.initialize(BrandAModel());
}
try {
await Firebase.initializeApp(
options: BaseBrandModel.instance.firebaseOptions,
);
print('Firebase initialized successfully: ${BaseBrandModel.instance.appName}');
} catch (e) {
print('Failed to initialize Firebase: $e');
}
This code selects the correct brand model based on the flavor and initializes Firebase accordingly.
To use the flavor-specific configurations, such as the logo, in your Flutter screens, you can leverage the BaseBrandModelclass that you have set up. Here’s how you can implement it:
Assuming you’ve defined the logo property in your BaseBrandModel, you can access it anywhere in your Flutter app using BaseBrandModel.instance.logo. Here’s an example of how you might use it in a Flutter screen:
Image.asset(BaseBrandModel.instance.logo)
5. Building and Running the Flavors
Finally, I built and ran each flavor using the following commands:
flutter run --flavor brandA
flutter run --flavor brandB
flutter run --flavor brandC -
These commands allowed me to generate builds for each flavor, ensuring that each version of the app was tailored to its specific market.
Conclusion
Using Flutter flavors to create a white-label application is a powerful technique that simplifies the management of multiple app versions. By centralizing the codebase and customizing the branding and configurations per flavor, I was able to efficiently produce a tailored experience for different markets.
This approach is not only time-efficient but also ensures consistency across all versions of the app. If you're working on a project that requires multiple branded versions, leveraging Flutter flavors is definitely the way to go.
Feel free to connect with me for more insights on Flutter development and white-label app creation!
Very helpful!