A Comprehensive Guide to Implementing Flutter Flavors
Introduction
Leveraging Flutter’s single code base for many platforms, developers can form a unified, cohesive, and good user experience for users on many platforms. But maintaining a diverse environment and configuration in your code base might be a cumbersome and tricky problem. Say hello to Flutter Flavors and wield a powerful weapon in our arsenal.
What are Flutter Flavors?
Flutter flavors are like build variants within Android or targets within iOS. Flavors allow us to configure an application in different ways for different environments, subject to no code duplication. This becomes even most useful when you’re doing testing and deployment.
Advantages of Using Flavors in Development
1. Different API Endpoints for Development and Production
Utilizing distinct API endpoints for development and production stages is essential in software development, particularly for mobile applications. Flavors allow developers to define separate configurations for these environments. For instance:
- Development Environment: This might use a test server where developers can experiment with new features, debug issues, and perform extensive testing without affecting real users. The API endpoint for this environment is typically something like dev.api.demoexamlpe.com.
- Production Environment: This refers to the live environment where real users engage with the application. It needs to be stable, secure, and performant. The API endpoint here would be something like api.demoexamlpe.com.
Using flavors ensures that these environments are kept separate, preventing accidental deployment of unfinished features to production and ensuring that development activities do not interfere with the live application.
2. Unique App Icons, Themes, and Names for Each Environment
Flavors allow an application to have distinct visual identities for different environments. This can include:
- App Icons: Different icons for development and production versions help developers and testers quickly identify which version of the app they are using. For example, a development app might have a small “DEV” badge on the icon.
- Themes: Different color schemes or themes can be applied to distinguish environments visually. A development app might use a blue theme, while the production app uses a green theme.
- Names: Implementing distinct app names for different build variants, like “MyApp (Development)” and “MyApp”, can streamline the development process.
This visual separation acts as a preventative measure against environment confusion, ensuring that developers, testers, and other project personnel are always aware of the specific app context.
3. Custom Configurations
By utilizing build flavors, developers can create distinct app configurations for various environments. Feature flags allow selectively enabling or disabling functionalities within specific build variants.
Feature Flags: Specific features can be enabled or disabled based on the environment. For instance, experimental features might be enabled in the development flavor but hidden in the production flavor.
- Debugging Tools: Development flavors can include additional debugging tools and logs that help developers identify and fix issues more efficiently. These tools and logs can be excluded from the production flavor to optimize performance and security.
- API Keys and Secrets: Independent API keys or secrets for each environment serve as a security cornerstone. By preventing resource crossover, such as employing distinct Google Maps API keys for development and production, potential issues and unnecessary costs are mitigated.
Custom configurations provided by flavors optimize the development workflow by enhancing flexibility, security, and efficiency. This ensures each environment aligns precisely with its intended purpose.
Configuration Files in Flutter
In this file, we have an enum Environment for the different environments (dev, uat, prod) and an abstract class AppEnvironment that holds the configuration values for each environment. The setUpEnv method sets the appropriate values based on the environment passed to it.
enum Environment { dev, uat, prod }
abstract class AppEnvironment {
static late String baseurl;
static late String environmentName;
static late String imagePath;
static late Environment _environment;
static Environment get environment => _environment;
static setUpEnv(Environment environment) {
_environment = environment;
switch (environment) {
case Environment.dev:
baseurl = ‘http://api.qa.aaaaaaaaaa.com’;
environmentName = ‘DEV’;
imagePath = ‘assets/images/dev/mang_image.png’;
break;
case Environment.uat:
baseurl = ‘http://api.qa.bbbbbbb.com’;
environmentName = ‘UAT’;
imagePath = ‘assets/images/uat/employee_image.png’;
break;
case Environment.prod:
baseurl = ‘https://api.cccccccccc.net’;
environmentName = ‘PROD’;
imagePath = ‘assets/images/prod/bo_image.png’;
break;
default:
baseurl = ”;
environmentName = ”;
imagePath = ”;
break;
}
}
}
AppEnvironment Class:
- Holds static variables for baseurl, environmentName, and imagePath.
- Contains a static method setUpEnv to set the configuration based on the environment passed.
setUpEnv Method:
- Accepts an Environment parameter.
- Sets the static variables (baseurl, environmentName, imagePath) according to the environment:
- dev: http://api.qa.aaaaaaaaaa.com, DEV, assets/images/dev/mang_image.png
- uat: http://api.qa.bbbbbbb.com, UAT, assets/images/uat/employee_image.png
- prod: https://api.cccccccccc.net, PROD, assets/images/prod/bo_image.png
- Default case resets the values to empty strings if the environment doesn’t match any predefined environment.
Common Entry Point
Next, we create a common entry point for the app that will be used across different environments.This commonmain function initializes the app with a MyApp widget. The MyApp widget configures the MaterialApp appropriately according to the specific environment
import ‘package:blog/welcome_page.dart’;
import ‘package:http/http.dart’ as http;
import ‘package:blog/flavor_config.dart’;
import ‘package:flutter/material.dart’;
void commonmain() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: ‘Flutter Flavor ${AppEnvironment.environmentName}’,
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(),
routes: {
‘/home’: (context) => const MyHomePage(),
‘/welcomePage’: (context) => const WelcomePage(),
},
);
}
}
Main Files for Each Flavor
We then create separate main files for each environment (DEV, UAT and PROD) that call the commonmain function after setting up the environment.
Main Files for DEV
This file imports the necessary modules and sets up the environment to dev using the AppEnvironment.setUpEnv method. After the environment is configured, it calls commonmain to run the app with development settings.
import ‘package:blog/common_main.dart’;
import ‘package:blog/flavor_config.dart’;
import ‘package:flutter/material.dart’;
void main() {
WidgetsFlutterBinding.ensureInitialized();
AppEnvironment.setUpEnv(Environment.dev);
commonmain();
}
Main Files for UAT
This file sets up the environment to uat for the User Acceptance Testing configuration. Like the other main files, it initializes the necessary bindings and then calls commonmain to run the app with UAT settings.
import ‘package:blog/common_main.dart’;
import ‘package:blog/flavor_config.dart’;
import ‘package:flutter/material.dart’;
void main() {
WidgetsFlutterBinding.ensureInitialized();
AppEnvironment.setUpEnv(Environment.uat);
commonmain();
}
Main Files for PROD
This file establishes the production environment by setting configuration values suitable for deployment. It ensures all necessary bindings are initialized and then calls commonmain to run the app with production settings.
import ‘package:blog/common_main.dart’;
import ‘package:blog/flavor_config.dart’;
import ‘package:flutter/material.dart’;
void main() {
WidgetsFlutterBinding.ensureInitialized();
AppEnvironment.setUpEnv(Environment.prod);
commonmain();
}
Configuring Flavors for Android and iOS in build.gradle file
Configure flavors for Android and iOS in our Flutter project to support multiple environments, customizing settings like application ID and resource values for each build.
flavorDimensions “default”
productFlavors {
dev {
dimension “default”
resValue “string”, “app_name”, “OC Management”
applicationIdSuffix “.dev”
}
uat {
dimension “default”
resValue “string”, “app_name”, “OC Developer”
applicationIdSuffix “.uat”
}
prod {
dimension “default”
resValue “string”, “app_name”, “OC Boss”
applicationIdSuffix “.prod”
}
}
flavorDimensions “default”: Defines a dimension for the flavors. A dimension is a category for flavors, which allows us to create multiple flavor dimensions if needed.
productFlavors: Defines different flavors (dev, uat, prod) and their specific configurations.
- dimension “default”: Associates each flavor with the “default” dimension.
- resValue “string”, “app_name”, … Sets a different app name for each flavor.
- applicationIdSuffix “.dev”: Adds a suffix to the application ID for each flavor to differentiate them.
What’s new we used …..?
Streamlining asset management across app flavors is now easier with Flutter 3.22’s new feature.we can specify which assets belong to which flavors in the pubspec.yaml file. Here’s how we can configure it:
assets:
– assets/common/
– path: assets/images/dev/
flavors:
– dev
– path: assets/images/uat/
flavors:
– uat
– path: assets/images/prod/
flavors:
– prod
Advantage of This Approach
Simplify Asset Management: Clearly organize and manage assets for different environments within a single configuration file.
Reduce Build Complexity: Automatically include only the relevant assets for each build, minimizing the risk of errors and reducing build times.
Improve Code Maintenance: Keep environment-specific assets separated, making it easier to update and maintain them without affecting other environments.
Commands to build and run our flavor
For Development : flutter run -t lib/main_dev.dart
For Staging : flutter run -t lib/main_staging.dart
For Production : flutter run -t lib/main_prod.dart
For launching app icon : flutter pub run flutter_launcher_icons:main -f flutter_launcher_icons*
FAQ’s
Que – Working with android studio could be easy to run the program
But while using vs code it dosen’t find the route
Ans – Working with android studio could be easy to run the program
But while using vs code it dosen’t find the route for this we have to
Set our launch.json
Example
{
“version”: “0.2.0”,
“configurations”: [
{
“name”: “blog (dev)”,
“request”: “launch”,
“type”: “dart”,
“program”: “lib/main_dev.dart”,
“args”: [“–flavor”, “dev”]
},
{
“name”: “blog (uat)”,
“request”: “launch”,
“type”: “dart”,
“program”: “lib/main_uat.dart”,
“args”: [“–flavor”, “uat”]
},
{
“name”: “blog (prod)”,
“request”: “launch”,
“type”: “dart”,
“program”: “lib/main_prod.dart”,
“args”: [“–flavor”, “prod”]
},
}
Que: What should I do if I receive a deprecated command warning when setting launcher icons?
Ans: If you receive a deprecated command warning, use dart run instead of flutter pub run. For example, use:
dart run flutter_launcher_icons:main -f flutter_launcher_icons.yaml
Que : What are the benefits of managing assets per flavor in Flutter 3.22?
Ans : compared to the older method of including all assets in every build, managing assets per flavor helps in optimizing build sizes by including only necessary assets, thereby reducing unnecessary bloat.