Когда приложение выходит за пределы пет-проекта, возникает необходимость держать окружения раздельно. Dev, Staging и Production должны жить своей жизнью: с разными API-ключами, бэкендом, а иногда даже иконками и названиями. В Flutter эта задача решается через Flavors. Рассказываю, как настроить и не запутаться.
Что такое Flavors и зачем они нужны:
Flavors (или схемы в iOS, продуктные варианты в Android) позволяют из одной кодовой базы собирать разные варианты приложения. У каждого варианта могут быть свои:
🔵URL бэкенда и ключи API.
🔵Имя приложения и bundle ID.
🔵Иконки и сплеш-скрины.
🔵Настройки сборки и зависимости.
Это дает возможность установить на устройство одновременно dev-версию и продовую, не боясь, что они перезатрут друг друга. И главное - исключает случайную отправку тестового кода в релиз.
Организация кода:
Самый простой способ - сделать отдельные точки входа для каждого окружения. В папке lib создаем файлы:
🔵main_dev.dart
🔵main_staging.dart
🔵main_prod.dart
В каждом передаем в приложение идентификатор среды, чтобы внутри можно было подставлять нужные конфиги.
void main() {
runApp(MyApp(environment: 'DEV'));
}
Android - настройка productFlavors:
В android/app/build.gradle добавляем секцию:
flavorDimensions "default"
productFlavors {
dev {
dimension "default"
applicationIdSuffix ".dev"
versionNameSuffix "-dev"
}
staging {
dimension "default"
applicationIdSuffix ".staging"
versionNameSuffix "-staging"
}
production {
dimension "default"
}
}
Это даст разные имена пакетов: .dev, .staging и основное. Приложения не будут конфликтовать при установке.
iOS - схемы и бандлы:
В Xcode нужно продублировать схему Runner для каждого окружения и задать разные идентификаторы бандла в настройках таргета. Например:
🔵com.example.app.dev
🔵com.example.app.staging
🔵com.example.app
В Info.plist можно выставить разные названия приложений, чтобы в меню было видно, какая версия запущена.
Запуск и сборка:
Для запуска нужного flavor используем флаги:
flutter run --flavor dev -t lib/main_dev.dart
flutter build apk --flavor prod -t lib/main_prod.dart
flutter build ios --flavor staging -t lib/main_staging.dart
Управление конфигурацией:
Внутри кода удобно сделать класс с константами для каждого окружения:
class AppConfig {
static const Map<String, String> apiUrls = {
'DEV': 'https://dev.api.example.com',
'STAGING': 'https://staging.api.example.com',
'PROD': 'https://api.example.com',
};
}
А в приложении просто обращаться по ключу, который пришел из main-файла.
Иконки для каждого flavor:
Пакет flutter_launcher_icons умеет генерировать иконки под разные flavors. В pubspec.yaml прописываем:
flutter_launcher_icons:
flavors:
dev:
image_path: "assets/icons/dev_icon.png"
staging:
image_path: "assets/icons/staging_icon.png"
production:
image_path: "assets/icons/prod_icon.png"
Запускаем и получаем разные иконки для каждого окружения.
Лучшие практики:
🔵Не храните ключи и секреты прямо в коде. Используйте .env файлы или безопасное хранилище.
🔵Ведите имена flavors одинаково на Android и iOS - меньше путаницы.
🔵Настройте CI/CD так, чтобы пайплайны собирали каждый flavor отдельно.
🔵Для тестировщиков dev-сборка должна визуально отличаться (иконка, цвет темы), чтобы случайно не перепутали с продакшеном.
🔗 Ссылка на подробную статью
💡 Вывод:
Flavors - это не про «сделать красиво», а про контроль и безопасность. Правильная настройка окружений убережет от случайных деплоев с тестовыми данными и позволит команде спокойно работать, не боясь что-то сломать в бою. Один раз настроив, вы сэкономите часы нервотрепки и багов на пустом месте.
➡️ Flutter & Dart | Мобильный трудоголик