Организация Flavors во Flutter

от автора

Для чего нужны Flavors

Представьте ситуацию: есть приложение с аналитикой. Есть команда разработки, тестировщики и конечные пользователи. И те, и те пользуются одной версией приложения. Допустим мы хотим проанализировать насколько пользователям интересна фича А. Что в этом случае мы делаем? Идём в аналитику и смотрим сколько было использований данной фичи (например, переходов на экран). Но что же мы видим: запредельное число переходов, которое ну никак невозможно с текущей аудиторией, причём все эти переходы были в какой-то определенный отрезок времени. Мы идём дальше и понимаем, что в это время проводились тесты данной фичи. А чуть ранее её разработка. При этом аналитика также отсылалась. Итог: аналитика получается грязной и некачественной.

Здесь можно заменить слово аналитика на любое другое: пуш-нотификации, креш-репортинг и т.д.

И в этой ситуации нас спасает разделение приложения на две версии отличающиеся минимально, например Bundle ID(package-name). Разработчики и тестеры используют только специальную dev версию, а пользователи продовую.

Это как раз и есть одна из задач flavor’ов. Здесь будет использоваться именно flavor, так как именно это название используется Flutter’ом. Люди, которые знакомы с Android-разработкой, думаю сразу узнали этот механизм.

Flavouring Flutter?

Хорошо, мы разобрались с задачей. Но как это реализовать? Всё ли так просто, как пишут?
Давайте сразу определимся: организация flavor’ов — чисто нативная задача. Сама информация о них не будет доступна из dart кода. Поэтому и за способами организации мы пойдём в нативную мобильную разработку.

Android

Здесь всё просто. Ничем не отличается от стандартных способов в android. Конечно может возникнуть вопрос: «А почему бы не использовать buildType?», но об этом позже.

Итак, всё, что нам потребуется в минимальном варианте:

flavorDimensions "release-type"      productFlavors {         dev {             dimension "release-type"             applicationIdSuffix  ".dev"             versionNameSuffix "-dev"         }          prod {             dimension "release-type"         }     }

И всё, теперь мы можем с лёгкостью запустить команду:

flutter run --flavor dev

на нашем android девайсе.

У некоторых вдумчивых разработчиков может возникнуть вопрос: «А почему не buildType?» Отвечаю: команда Flutter захардкодила buildType под свои нужды. Собственно, вся магия дебаг сборки в этом и заключается.

Немного о типах сборок и различных конфигурациях.
Итак, мы вспомнили про builtTypes. Тут надо немного поговорить о них и об их аналогах в IOS.
Можно провести следующее соответствие:

Android IOS
build types build configurations
flavors targets

И типы сборок и конфигурации — это скорее нечто влияющее на саму сборку, в идеале не влияющее на кодовую базу или различия в приложениях (хотя вопрос спорный). А вот flavor’ы и target’ы — вполне удобный инструмент для создания двух приложений из одного и настройки отличий версии для разработчиков от версии пользователей.

И всё бы было хорошо, и настраивалось именно так, если бы не одно «но»…

Runner — захардкоженный таргет.

Как оказалось, использовать target для реализации flavors на стороне iOS невозможно. Дело в том, что команда разработчиков Flutter по некоторым причинам подвязалась на это имя. И на этом можно было бы закончить всю работу, казалось бы. Но нет. Ведь можно использовать конфигурации сборки.

IOS

Задача: реализовать две конфигурации (dev, prod, отличающиеся наличием суффикса у версии для разработчиков).

Решение:

  1. Создаём две конфигурации.
  2. В разработческой указываем суффикс.
  3. Профит!

А теперь разберём подробнее.

Файлы конфигураций

В наших проектах имеется две конфигурации: dev, prod. Содержимое у них примерно следующее:

    #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug-dev.xcconfig"     #include "Generated.xcconfig"     #include "common.xcconfig"      bundle_suffix=.dev     IDENTIFIER=$(identifier)$(bundle_suffix)

Как мы видим, в них задаётся bundle_suffix.

К слову, кроме этого сам Flutter имеет конфигурации Release и Debug. В них надо также добавить bundle_suffix. Мы же не хотим, чтобы по умолчанию наша версия была версией для пользователей при запуске из своей любимой IDE.

Можно видеть некоторый параметр IDENTIFIER — с ним мы ознакомимся ниже.

Итак, создаём две конфигурации и располагаем в следующих путях:

ios/Flutter/dev.xcconfig ios/Flutter/prod.xcconfig

Создание можно провернуть и через XCode (даже лучше, чтобы они добавились именно как конфигурационные файлы). Делается это с помощью щелчка правой кнопки по Runner → New File → Configuration Settings File → дальше выбираем место сохранения.

Build Configurations. Умножай на два.

Теперь разберёмся с конфигурациями сборки. Открываем Runner.xcworkspace в Xcode и выбираем представление Project.

Там находим кнопку «+» в разделе Configurations и создаём четыре конфигурации: две для Release и две для Debug, где постфиксом пишем название нашего конфига и будущей схемы приложения.

Выглядеть это будет примерно так:

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

Добавление Scheme

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

Их создание крайне простое. Важный момент: выберите правильный таргет — Runner.
Теперь выберите пункт Edit Scheme и проставьте необходимые конфигурации на каждом из этапов для схем.

Обновление Info.plist

И последний штрих (спойлер: далеко не последний) — устанавливаем параметр Bundle Identifier в Info.plist как

$(PRODUCT_BUNDLE_IDENTIFIER)$(bundle_suffix)

И вроде бы всё…

И вроде бы всё сделали, проект запускается, на Android всё вообще круто, но если вы вдруг решили использовать fastlane gym для подписи iOS — не получится. И вообще, IOS подпись приложения почему-то не работает… Давайте разберёмся.

No Provisioning Profile

Первая проблема при выгрузке — не найден профайл. Причём в ошибке выведен не тот идентификатор, что мы указали в конфиге.

Как оказалось, установка идентификатора через Info.plist не срабатывает, gym смотрит именно на PRODUCT_BUNDLE_IDENTIFIER, а он у нас одинаковый для всех конфигураций.
Помните загадочный файл common.xcconfig и параметр IDENTIFIER? Вот как раз они и решают эту проблему.

Создаём ещё один конфигурационный файл, в котором мы будем устанавливать по, сути, базовую часть нашего PRODUCT_BUNDLE_IDENTIFIER.

Содержимое описано одной строкой:

identifier=your.bundle.identifier

Подключаем этот файл через include в остальные конфиги и устанавливаем новую User Defined
переменную IDENTIFIER:

#include "common.xcconfig"  IDENTIFIER=$(identifier)$(bundle_suffix)

Теперь придётся немного поработать мышкой внутри Xcode. Переходим в наш таргет на вкладку Build Settings:

Далее в поиске ищем Product Bundle Identifier (раздел Packaging):

И меняем значение для всех конфигов на:

$(IDENTIFIER)

Теперь переходим в Info.plist и убираем из строки с идентификатором bundle suffix, оставляя только :

$(PRODUCT_BUNFLE_IDENTIFIER)

Пробуем собрать и подписать. Всё работает как необходимо.

Разные файлы для разных bundle id

Но мы решили подключить аналитику. Если мы используем Firebase, то понадобится два проекта и четыре приложения соответственно (две платформы под две версии).

И что самое главное — нам необходимо иметь два файла google-services.json(Google-Services.Info.plist). На стороне Android всё будет просто: мы создаем папку с названием нашего flavor’а и закидываем туда наш файл.

А вот с IOS нас ждёт крутое приключение с шелл-скриптами и фазами сборки.

Создание и размещения файлов

Первым делом необходимо создать в проекте папку, где будут храниться эти файлы. Мы используем следующую структуру:

Важно: не создаём их через XCode. Они не должны быть привязаны к проекту. Если всё же XCode — это ваша любимая IDE, при создании снимите галочки с пункта Add to target.
Далее располагаем наши файлы в соответствующих папках.

Добавление файлов в приложение во время сборки

Так как файлы не привязаны к проекту, в целевой архив они не попадут. А чтобы они всё-таки туда попали, надо в ручную их туда добавить.

Добавим дополнительный этап сборки в виде Run Script (setup firebase как пример названия):

Обратите внимание на расположение, оно играет решающую роль!

Теперь добавим сам скрипт, как один из вариантов можно использовать подобный:

# Name of the resource we're selectively copying GOOGLESERVICE_INFO_PLIST=GoogleService-Info.plist  # Get references to dev and prod versions of the GoogleService-Info.plist # NOTE: These should only live on the file system and should NOT be part of the target (since we'll be adding them to the target manually) GOOGLESERVICE_INFO_DEV=${PROJECT_DIR}/${TARGET_NAME}/Firebase/dev/${GOOGLESERVICE_INFO_PLIST} GOOGLESERVICE_INFO_PROD=${PROJECT_DIR}/${TARGET_NAME}/Firebase/prod/${GOOGLESERVICE_INFO_PLIST}  # Make sure the dev version of GoogleService-Info.plist exists echo "Looking for ${GOOGLESERVICE_INFO_PLIST} in ${GOOGLESERVICE_INFO_DEV}" if [ ! -f $GOOGLESERVICE_INFO_DEV ] then echo "No Development GoogleService-Info.plist found. Please ensure it's in the proper directory." exit 1 # 1 fi  # Make sure the prod version of GoogleService-Info.plist exists echo "Looking for ${GOOGLESERVICE_INFO_PLIST} in ${GOOGLESERVICE_INFO_PROD}" if [ ! -f $GOOGLESERVICE_INFO_PROD ] then echo "No Production GoogleService-Info.plist found. Please ensure it's in the proper directory." exit 1 # 1 fi  # Get a reference to the destination location for the GoogleService-Info.plist 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 if [[ "${CONFIGURATION}" == *-prod ]] then echo "Using ${GOOGLESERVICE_INFO_PROD}" cp "${GOOGLESERVICE_INFO_PROD}" "${PLIST_DESTINATION}" else echo "Using ${GOOGLESERVICE_INFO_DEV}" cp "${GOOGLESERVICE_INFO_DEV}" "${PLIST_DESTINATION}" fi

Мысли после

С помощью этих довольно хитрых манипуляций мы настроили флейворинг. При этом, я очень надеюсь, что решение временное и в скором времени завезут систему сборки под Flutter (ведутся активные работы). Но пока мы имеем то, что имеем. И делаем жизнь проще своими руками.

ссылка на оригинал статьи https://habr.com/ru/company/surfstudio/blog/503864/


Комментарии

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

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