Flutter Flavoring

от автора

Хотелось ли вам иметь несколько версий одного приложения?

Чтобы одной командой вы могли собрать приложение под определенное окружение?

Сталкивались ли вы с тем, что одновременно нельзя было установить несколько версий одного приложения на одном устройстве?

Всем привет!

Меня зовут Андрей!

И в этой статье я расскажу, как настроить сборку приложения для разных окружений.

Сразу отмечу, что слова версия, окружение и флейвор (flavor) будут взаимозаменяемыми.

Не смотря на то, что материал называется Flutter Flavoring, бОльшая часть работы будет в нативном пространстве (в папках android/ и ios/). Приведённые мной инструкции используются так же и для нативных приложений, а не только для Flutter приложений.

  • Overview

  • Create the App

  • Переменные окружения в .env

  • Android Flavoring

  • iOS Flavoring

  • App Icons

  • Firebase Projects

  • Заключение

GitHub: https://github.com/AndrewPiterov/flutter_starter_app/

Видео версия на YouTube:

Overview

Мы настроим сборку приложения для двух окружений: DEVELOPMENT и PRODUCTION.

У каждой версии будут свои

  • иконки

  • наименования

  • application ID

  • переменные окружения, т.к. адрес к API серверу

  • Firebase проекты

Начнём…

Create the App

Для начала создадим наш новый флаттер проект и мигрируем его сразу на null safety

$ flutter create flutter_starter_app $ cd flutter_starter_app && dart migrate --apply-changes

Откроем проект в любимом IDE.

Переменные окружения в .env

Первым делом настроим переменные окружения для нашего проекта.

Эти переменные я предпочитаю хранить в файле assets/.env. И в зависимости какую версию приложения мы собираем, мы указываем в этом файле соответствующие переменные. Изменять этот файл будем в CI/CD (Continuous integration & continuous delivery) в следующих статьях, а пока укажем значения в этом файле один раз и продолжим.

# assets/.env  ENVIRONMENT=dev API_URI=https://api.mydev.com

Добавим в pubspec.yaml пакет flutter_dotenv, который облегчит нам считывание этого .env файла:

dependencies: 		# ...     flutter_dotenv: ^4.0.0-nullsafety.0

И укажем, что вместе с проектом идут следующие файлы (assets):

assets:     - assets/

Добавляем класс, который будет считывать наши переменные с этого .env файла и предоставлять доступ к этим переменным через свойства:

import 'package:flutter/foundation.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart' as DotEnv;  class AppConfig {   factory AppConfig() {     return _singleton;   }    AppConfig._();    static final AppConfig _singleton = AppConfig._();    static bool get IS_PRODUCTION =>       kReleaseMode || ENVIRONMENT.toLowerCase().startsWith('prod');    static String get ENVIRONMENT => env['ENVIRONMENT'] ?? 'dev';    static String get API_URI => env['API_URI']!;    Future<void> load() async {     await DotEnv.load(fileName: 'assets/.env');     debugPrint('ENVIRONMENT: $ENVIRONMENT');     debugPrint('API ENDPOINT: $API_URI');   } }

Подгрузим наши переменные окружения в самом начале запуска приложения в main.dart:

Future main() async {   WidgetsFlutterBinding.ensureInitialized();    await AppConfig().load();    runApp(MyApp()); }

И где-то на скрине в приложении отобразим наши переменные:

Column(   mainAxisAlignment: MainAxisAlignment.center,   children: [     Text(       AppConfig.ENVIRONMENT,     style: TextStyle(fontSize: 50),     ),     Text(       AppConfig.API_URI,         style: TextStyle(fontSize: 30),     ),   ], )

Запускаем приложение:

$ flutter run

Результат:

Изменим значения в .env, перезапустим приложение, и увидим новые значения на экране.

❗️❗️❗️ Не забудьте поместить .env в .gitignore ❗️❗️❗️

На этом настройка в Flutter пространстве (в папке lib/) закончена, следующие настройки будут в нативном пространстве, т.е. в папках android/ и ios/.

Android Flavoring

Для Android настройка очень простая. Достаточно указать следующие параметры в android/app/gradle

android {     compileSdkVersion 30  		// ...      flavorDimensions "starter_app"      productFlavors {         dev {             dimension "starter_app"             applicationIdSuffix ".dev"             resValue "string", "app_name", "Starter(Dev)"             versionNameSuffix ".dev"         }         prod {             dimension "starter_app"             resValue "string", "app_name", "Starter"         }     }

Где указали какие флейворы нам нужны, и у каждого флейвора свой applicationId и наименование.

В AndroidManifest.xml укажем ссылку на переменную app_name с наименованием из флейвора:

<application         ...         android:label="@string/app_name"

Запускаем приложение на Android под каждую версию:

$ flutter run --flavor=dev $ flutter run --flavor=prod

Результат: установилось два приложения с разными наименованиями.

iOS Flavoring

В iOS нет такого понятия как Flavor, которое есть в Android.И в iOS используется Схемы (Schema) и их Конфигурации (Configuration).

На картинке ниже изображено, что у каждой Схемы есть свои Конфигурации. И у каждой Конфигурации есть свои параметры, которые мы можем кастомизировать. Например, applicationId, название приложения и иконки приложения под разные версии.

Первым делом нам нужно добавить наши Схемы, и добавить к каждой схеме её конфигурации. Для этого мы откроем XCode, и сверху нажимаем на Runner -> New scheme и добавляем нашу новую dev Схему.

Далее добавим devконфигурации. Для этого выбираем Project -> Runner, где видим раздел наших Конфигураций. Чтобы добавить новые конфигурации, нам нужно продублировать имеющиеся конфигурации и назвать их соответсnвующим образом с суффиксом -dev, например:

Дальше переименуем нашу Runner схему вprod

Далее нужно привязать dev Конфигурации к dev схеме. На текущий момент у dev схемы указаны Debug, Release, Profile конфигурации (те, что без суффикса -dev), т.к. мы создали новую dev схему когда еще не было -dev конфигураций.

Переименуем Debug, Release, Profile, добавив к ним суффикс -prod:

Сейчас у нас две схемы с их отдельными конфигурациями. И мы можем кастомизировать параметры для каждой отдельной схемы.  И первым делом, выставим каждой конфигурации свой applicationId:

Кастомизируем наименование приложения для каждой отдельной конфигурации:

И добавим в ios/Runner/Info.plist новое свойство для нашей переменной:

<dict> ... <key>CFBundleDisplayName</key> <string>$(APP_DISPLAY_NAME)</string> ... </dict>

Запускаем приложение на iOS под каждую версию:

$ flutter run --flavor=dev $ flutter run --flavor=prod

Результат: установилось два приложения с разными наименованиями.


App Icons

Мы воспользуемся плагином flutter_launcher_icons, который сгенерирует для нас иконки для каждой платформы и для каждой версии по отдельности.

dev_dependencies:     # ...     flutter_launcher_icons: ^0.8.1

Добавим в корне проекта файлы конфигурации для этого плагина под каждую версию, в которых укажем какие картинки брать для генерации иконок.

# flutter_launcher_icons-dev.yaml  flutter_icons:   android: true   ios: true   # image_path: "assets/app_icon/dev.jpg"   image_path_android: "assets/app_icon/android_dev.png"   image_path_ios: "assets/app_icon/ios_dev.png"
# flutter_launcher_icons-prod.yaml  flutter_icons:   android: true   ios: true   # image_path: "assets/app_icon/prod.jpg"   image_path_android: "assets/app_icon/android_prod.png"   image_path_ios: "assets/app_icon/ios_prod.png"

Запускаем следующую команду генерации иконок:

flutter pub run flutter_launcher_icons:main -f flutter_launcher_icons*

И посмотрим, где добавились сгенерированные иконки:

Для Android все готово, но для iOS нужно снова вернуться в XCode и так же, как и в случае с наименованием и application ID, указать у каждой конфигурации свою иконку:

Запускаем приложение под каждую версию на iOS и Android, и увидим результат — иконки наших уже установленных приложений обновились:


Firebase Projects

Прежде всего создадим два Firebase проекта под каждую версию через firebase console .

В каждом проекте добавим Android и iOS приложения и скачаем файлы конфигурации Firebase проектов:

  • google-services.json Android приложения — 2 штуки

  • GoogleService-Info.plist iOS приложения — 2 штуки

Для теста, можем для каждого Firebase проекта активировать Firestore, в котором одна коллекция secrets с одним элементом, у которого есть поле value. У prod версии значение в value равно PRODUCTION, у dev версии — DEVELOPMENT.

В pubscpec.yaml добавляем Firebase зависимости

dependencies: 		# ...     # Firebase     firebase_core: ^1.1.0     cloud_firestore: ^2.0.0

В main.dart проинициализируем Firebase приложение

Future main() async { 	// ... 	await Firebase.initializeApp();      runApp(MyApp()); }

И для теста, где-то на скрине приложения отобразим наше значение value

StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(   stream:       FirebaseFirestore.instance         .collection('secrets').snapshots(),   builder: (_, snapshot) {     if (!snapshot.hasData || snapshot.data!.docs.isEmpty) {       return CircularProgressIndicator();     }      final first = snapshot.data!.docs.first.data();     return Text(       'Firebase: ' + first['value'],       style: TextStyle(         fontSize: 25,         fontWeight: FontWeight.bold,         color: Colors.blue,       ),     );   }, ),

Настроим iOS и Android для Firebase. Более подробно о настройке можно почитать на официальном сайте.

Настройка Firebase на iOS

В файле ios/Podfile укажем минимальную версию iOS 10

platform :ios, '10'

И в этом же фале в методе target 'Runner' добавим следующую строчку, из-за которой наше приложение будет собираться быстрее:

# ... target 'Runner' do   pod 'FirebaseFirestore', :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', :tag => '7.11.0' # ... end

Далее кладем файлы конфигурации для Firebase в проекте в папках config/prod и config/dev

И добавим новый Build Phase Script, указанный ниже, который будет во время сборки определенной версии приложения брать соответсвующий файл Firebase конфигурации и помещать его в папку Runner:

environment="default"  # Regex to extract the scheme name from the Build Configuration # We have named our Build Configurations as Debug-dev, Debug-prod etc. # Here, dev and prod are the scheme names. This kind of naming is required by Flutter for flavors to work. # We are using the $CONFIGURATION variable available in the XCode build environment to extract  # the environment (or flavor) # For eg. # If CONFIGURATION="Debug-prod", then environment will get set to "prod". if [[ $CONFIGURATION =~ -([^-]*)$ ]]; then environment=${BASH_REMATCH[1]} fi  echo $environment  # Name and path of the resource we're copying GOOGLESERVICE_INFO_PLIST=GoogleService-Info.plist GOOGLESERVICE_INFO_FILE=${PROJECT_DIR}/config/${environment}/${GOOGLESERVICE_INFO_PLIST}  # Make sure GoogleService-Info.plist exists echo "Looking for ${GOOGLESERVICE_INFO_PLIST} in ${GOOGLESERVICE_INFO_FILE}" if [ ! -f $GOOGLESERVICE_INFO_FILE ] then echo "No GoogleService-Info.plist found. Please ensure it's in the proper directory." exit 1 fi  # Get a reference to the destination location for the GoogleService-Info.plist # This is the default location where Firebase init code expects to find GoogleServices-Info.plist file PLIST_DESTINATION=${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app echo "Will copy ${GOOGLESERVICE_INFO_PLIST} to final destination: ${PLIST_DESTINATION}"  # Copy over the prod GoogleService-Info.plist for Release builds cp "${GOOGLESERVICE_INFO_FILE}" "${PLIST_DESTINATION}"

Называем эту Build Phase понятным именем и перемещаем ее немного выше:

❗️❗️❗️ Не забудьте поместить GoogleService-Info.plist в .gitignore ❗️❗️❗️

Запускаем приложение и видим результат.

Настройка Firebase на Android

Первое добавим зависимость для плагина google services в android/build.gradle

# android/build.gradle  buildscript {   dependencies {     // ... other dependencies     classpath 'com.google.gms:google-services:4.3.3'   } }

Используем плагин в android/app/build.gradle

apply plugin: 'com.google.gms.google-services'

Выставим минимальную версию SDK как 21

android {     defaultConfig {         // ...         minSdkVersion 21            // <------ THIS         targetSdkVersion 28         multiDexEnabled true     } }

Добавим файлы конфигурации Firebase в соответствующие папки каждого флейвора:

❗️❗️❗️ Не забудьте поместить google-services.json в .gitignore ❗️❗️❗️

Запускаем каждую версию на Андроиде и проверяем результат:


Заключение

Таким образом, мы настроили флейворы или сборку разных версий нашего приложения, что у каждой версии свои:

  • application id

  • иконки

  • наименования

  • переменные окружения

  • Firebase бэкенд

Надеюсь материал был полезен для вас.

Всем happy coding!

ссылка на оригинал статьи https://habr.com/ru/post/556382/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *