В основном, про Firebase рассказывают в контексте создания приложений под IOS или Android. Однако, данный инструмент можно использовать и в других областях разработки, например при создании Telegram ботов. В этой статье хочу рассказать и показать насколько Firebase простой и удобный инструмент (а ещё и бесплатный, при разумных размерах проекта).
Ровно посередине апреля я очнулся от работы и вдруг вспомнил, что у меня ещё не написан диплом, а сдавать его через месяц - полтора. У меня была заброшенная научная статья на достаточно скучную тему, делать на её базе диплом мне уж совсем не хотелось. Нужен был глоток свежего воздуха - какой-нибудь новый проект.
В середине февраля я с ребятами из веб студии обсуждал идею создания приложения по подбору квартир с рекомендательной системой, которая анализировала бы изображения интерьеров и подстраивалась под предпочтения пользователя. Так как мой диплом должен быть на тему Computer Vision, то я решил развить эту тему. Да, и было придумало прикольное название - Flinder (Flats Tinder).
Если погружаться в детали, то для создания рекомендательной системы, работающей с изображениями, необходимо построить пайплайн конструирования эмбедингов на базе изображений. Эмбединг - это вектор, содержащий в себе информацию о изображении и являющийся результатом работы нейронной сети.
В частности, меня вдохновила одна научная статья про DeViSE: A Deep Visual-Semantic Embedding Model. Мне было интересно попробовать такие эмбединги.
В чём суть?Если кратко, то авторы статьи обучили нейронную сеть предсказывать не конкретные классы изображений, по типу "кошка", "собака", а векторные представления названий классов. Это те самые векторные представления, для которых "King - Man + Woman = Queen".
Такой подход, объединяет визуальное представление нейросети об изображении со структурой языка. Это приводит, например, к тому что модель может предсказать класс, которого не было в датасете, но который семантически близок к классу из датасета.
Для рекомендательной системы важно настроить алгоритм сбора данных о взаимодействии пользователей с контентом. В такой ситуации сложно придумать что-то более быстрое в разработке, чем телеграм бот.
Итак, телеграм бот:
Присылает пользователю изображение и просит его оценить
Получает оценку от пользователя
Сохраняет оценку пользователя в базу данных
*киллер фича* - удаляет изображение из диалога, если оно не понравилось пользователю
На первом этапе работы бот присылает пользователю случайные изображения. Но при последующем развитии проекта и интеграции рекомендательной системы бот будет присылает всё более и более релевантные фотографии.
Да, также важно раздобыть контент, который пользователи будут оценивать. Немного заморочившись я скачал сразу 20.000 изображений с интерьерами с Pinterest. Это были и запросы как скандинавский интерьер квартиры так и готический интерьер дома. Старался собрать как можно более разнообразный (репрезентативный) набор изображений.
Изображения добывал с помощью библиотечки pinterest-image-scraper (Там есть баги и она не супер удобная, но мне её хватило).
Меня немного смущал момент отправки изображений телеграм ботом. Получившаяся база изображений в 20.000 штук весила примерно 1.5 гигабайта и мучаться с переносом её на сервер мне уж совсем не хотелось.
Тут я подумал о том, что было бы классно выложить все фотографии на какой-нибудь облачный сервис, и дальше в телеграмм боте использовать только ссылки на изображения, а не сами изображения. А ссылки на изображения (или идентификаторы изображений) и оценки пользователей можно хранить в Firebase Realtime Database.
Перед этим я быстро проверил, поддерживает ли библиотека
pyTelegramBotAPI отправку
изображений ссылками. Метод send_photo
спокойно
работал с ссылками на изображения, так что я решил дальше
использовать этот подход.
Прежде я работал только с Firebase Realtime Database, но про удобство Firebase Storage был наслышан.
Итак, чтобы начать работать с Firebase вам необходимо зарегистрироваться на этом сервисе и создать там проект. После чего у вас откроется вкладка Project Overview.
Project OverviewДля того чтобы получить доступ к функциям Firebase из кода необходимо скачать ключи доступа к проекту. Сделать это можно нажав на значок шестерёнки в верхнем левом углу, справа от надписи Project Overview, и выбрать пункт Project Settings. Затем, на открывшемся экране нужно выбрать Service Accounts и нажать Generate new private key.
После чего, выбрав в левой вкладке меню Realtime Database и Storage можно создать соотвествующие базы данных. В проект на питоне подключить эти базы данных можно следующим способом.
import firebase_adminfrom firebase_admin import credentialsfrom firebase_admin import dbfrom firebase_admin import storagecred = credentials.Certificate("/path/to/secret/key.json")default_app = firebase_admin.initialize_app(cred, { 'databaseURL': 'https://realtime-db-name', 'storageBucket' : 'storage-bucket-local-name'})bucket = storage.bucket()
Где правые части внутри выражения initialize_app
есть условные ссылки на названия ваших баз данных внутри проекта в
Firebase. После инициализации у вас будут доступны две базы
данных
db
- объект Realtime Database. Данные хранятся в
виде одного JSON дерева. В случае работы с питоном - это по сути
объект dict.
bucket
- объект Storage, по сути, обёртка над
Google Storage, позволяющая по API загружать и скачивать
объекты.
Далее покажу несколько примеров использования этих двух баз данных.
Обожаю эту базу данных и готов петь ей дифирамбы. Она очень удобная, быстрая, надёжная, а главное - никакого SQL! Это JSON based Database. Но хватит похвалы, давайте посмотрим, как с ней работать.
Например, у нас есть несколько пользователей, которые хранятся в
users_database
.
users_databse = {"1274981264" : {"username" : "user_1","last_activity" : 1619212557},"4254785764" : {"username" : "user_2","last_activity" : 1603212638}}
Добавить их в в Realtime Database мы можем так:
db.reference("/users_databse/").set(users_databse)
Также мы можем добавить и следующего пользователя таким вот образом:
user_3_id = "2148172489"user_3 = {"username" : "user_3","last_activity" : 1603212638}db.reference("/users_database/" + user_3_id).set(user_3)
Этот код добавит user_3
в
users_database
Получить данные можно так.
user_3 = db.reference("/users_database/" + user_3_id).get()users_databse = db.reference("/users_databse/").get()
Это вернет объекты формата Python dict
Стоит отметить, что массивы в Realtime database хранятся в следующем виде.
a = ["one", "two", "three"]firebase_a = {"0" : "one","1" : "two","2" : "three"}
То есть также в формате json
И ещё один нюанс, Realtime Database не хранит объекты
None
и []
То есть код
db.reference("/users_database/" + user_3_id).set(None)
Приведёт к ошибке
А код
db.reference("/users_database/" + user_3_id).set([])
Удалит данные user_3
Также стоит добавить, что если внутри вашего объекта в питоне
есть какое-либо поле, значение которого есть None
или
[]
, то в объекте, загруженном в Realtime Database этих
полей не будет. То есть:
user_4 = {"username" : "user_4","last_activity" : 4570211234, "interactions" : []}# Но user_4_in_fb = {"username" : "user_4","last_activity" : 4570211234}
На самом деле, методами get()
и set()
всё не ограничивается. По ссылке вы можете посмотреть
документацию по firebase_admin.db
Вернёмся к Firebase Storage. Допустим, у нас на локальном диске
хранится изображение по пути image_path
Следующий код
добавит это изображение в Storage.
def add_image_to_storage(image_path): with open(image_path, "rb") as f: image_data = f.read() image_id = str(uuid.uuid4()) blob = bucket.blob(image_id + ".jpg") blob.upload_from_string( image_data, content_type='image/jpg' )
Где image_id
- уникальный идентификатор
изображения.
С получением доступа к изображению всё чуточку сложнее.
blob
имеет формат
blob.__dict__ = {'name': 'one.jpg', '_properties': {'kind': 'storage#object', 'id': 'flinder-interiors/one.jpg/1619134548019743', 'selfLink': 'https://www.googleapis.com/storage/v1/b/flinder-interiors/o/one.jpg', 'mediaLink': 'https://storage.googleapis.com/download/storage/v1/b/flinder-interiors/o/one.jpg?generation=1619134548019743&alt=media', 'name': 'one.jpg', 'bucket': 'flinder-interiors', 'generation': '1619134548019743', 'metageneration': '1', 'contentType': 'image/jpg', 'storageClass': 'REGIONAL', 'size': '78626', 'md5Hash': 'OyY/IkYwU3R1PlYxeay5Jg==', 'crc32c': 'VfM6iA==', 'etag': 'CJ+U0JyCk/ACEAE=', 'timeCreated': '2021-04-22T23:35:48.020Z', 'updated': '2021-04-22T23:35:48.020Z', 'timeStorageClassUpdated': '2021-04-22T23:35:48.020Z'}, '_changes': set(), '_chunk_size': None, '_bucket': <Bucket: flinder-interiors>, '_acl': <google.cloud.storage.acl.ObjectACL at 0x7feb294ff410>, '_encryption_key': None}
Где есть selfLink
и mediaLink
, однако
доступ к изображению по этим ссылкам - ограничен и доступен только
при наличии определенных прав доступа, которые настраиваются в
консоли Firebase.
В своём проекте я постарался сделать всё максимально просто и
поэтому воспользовался методом
blob.generate_signed_url(...)
. Этот метод генерирует
ссылку, которая имеет определённое время жизни. Время жизни ссылки
является параметром метода.
Следующий метод генерирует ссылку, живущую 10 минут.
def get_image_link_from_id(image_id): blob = bucket.blob(image_id + ".jpg") time_now = int(time.time() // 1) ttl = 600 return blob.generate_signed_url(time_now + ttl)
Не буду вдаваться в подробности написания телеграм ботов, так как на эту тему статей много (простой туториал, супер подробная статья). Пройдусь только по основным моментам.
Как выглядит бот?В своём проекте на pyTelegramBotAPI я
использовал InlineKeyboardButton
, состоящую из эмоджи
и callback_query_handler
, обрабатывающий нажатия на
кнопки.
keyboard = types.InlineKeyboardMarkup(row_width = 3)nott = types.InlineKeyboardButton(text="no_emoji", callback_data='no')bad = types.InlineKeyboardButton(text="bad_emoji", callback_data='bad')yes = types.InlineKeyboardButton(text="yes_emoji", callback_data='yes')keyboard.add(nott, bad, yes)
Небольшой баг хабра
Пока писал статью столкнулся с тем, что редактор статей Хабр в браузере Safari не переваривает эмоджи внутри вставок с кодом. Если что, в моём боте кнопки имеют такой вот вид, ниже скрин кода.
Реакции пользователей я храню в Realtime Database. Добавляю их туда следующим образом.
def push_user_reaction(chat_id, image_id, reaction): db_path = "/users/" + str(chat_id)+ "/interactions/"+ str(image_id)db.reference(db_path).set(reaction)
База данных Firebase Realtime Database имеет следующий вид
users
- база данных пользователей.
Для каждого пользователя в разделе interactions
мы
храним взаимодействия пользователя с изображениями.
last_image_id
и last_message_id
-
элементы логики работы телеграмм бота. Что-то типо конечного автомата.
Да, и идентификаторы пользователей в базе данных - это telegram
id пользователей (chat_id
для библиотеки telebot).
interiors_images
- база идентификаторов
изображений. В ней хранятся метаданные изображений, а также
дублируются реакции пользователей.
Ну и images_uuids
в Realtime Database - это просто
массив с уникальными идентификаторами изображений. Своего рода
костыль, чтобы было откуда выбирать идентификаторы изображений.
Собственно говоря сам костыль.
IMAGES_UUIDS = Nonedef obtain_images_uuids(): global IMAGES_UUIDS IMAGES_UUIDS = db.reference("/images_uuids/data").get()obtain_images_uuids()def get_random_image_id(): image_id = np.random.choice(IMAGES_UUIDS) return image_id
Да, на первом этапе, изображения, отправляемые пользователю, выбираются случайным образом.
Firebase Storage выглядит таким вот образом. Можно заметить, что названия изображений в Storage есть просто идентификаторы изображений + их расширение.
Также, огромным плюсом облачных баз данных является то, что можно хранить состояния взаимодействия пользователя с ботом. И если вдруг сервер решит перезапустить код бота, то это состояние сохранится. (Хотя если пользоваться обычной базой данных состояние тоже сохранится, так что это такой, притянутый за уши плюс)
Вообще, из недостатков библиотеки pyTelegramBotAPI - это то, что она медленная. И под напором сообщений от большого количества пользователей что-то вполне может пойти не так. Так что в будущем я планирую использовать другую, более мощную библиотеку. Проект в нынешнем виде - это буквально один вечер работы и поэтому я использовал знакомые мне инструменты, с которыми я уже работал.
Последнее время я стал адептом докера, поэтому и этот свой проект я размещал на сервер с помощью докера. Пройдусь по основным моментам, которые я использовал в проекте.
Dockerfile
FROM python:busterCOPY requirements.txt /tmp/RUN pip install -r /tmp/requirements.txtRUN mkdir /srcWORKDIR /srcCOPY ./src .CMD python3 /src/code/bot.py
requirements.txt
pyTelegramBotAPIfirebase-admingoogle-cloud-storagenumpy
Где src
- это место монтирования docker volume,
который я создал до этого командой
docker volume create \ --opt type=none \ --opt o=bind \ --opt device=/home/ubuntu/Flinder/src \ --name flinder_volume
После чего собрал образ и запустил контейнер следующим образом,
где флаг-v
монтирует созданные ранее
flinder_volume
в директорию src
внутри
докер контейнера.
docker run -d \--network=host \--name flinder_bot \--restart always \-v "flinder_volume:/src" devoak/flinder:1.0
Ну и полезное замечание, что у команды docker run
можно указать прекрасный параметр --restart always
,
который обеспечит постоянную работу бота на сервере.
До этого я делал через systemctl
, что было сложнее
и менее удобно.
Надеюсь, моя статья была полезна. Хочу привнести в сообщество программистов такую идею, что работать с Firebase - это очень просто, приятно и удобно, а главное - бесплатно, при разумных размерах проекта.
Более того, использование Firebase не ограничивается Телеграм ботами, недавно я сделал целый промышленный парсер инстаграмма на основе Firebase Realtime Database, о чём я тоже планирую написать статью.
Касательно моего проекта, буду благодарен, если вы повзаимодейтсвуете с моим ботом, там собраны классные фотографии интерьеров. Несколько часов сидел на Пинтересте и выбирал наиболее эстетичные категории. Добавил даже интерьеры квартир с двойным светом и интерьеры домов в стиле шале. С помощью бота планирую собрать данные для того чтобы проверить гипотезу о том, что эмбединги DeViSE работают эффективнее обычных эмбедингов изображений в контексте рекомендательных систем.
По поводу других проектов, оставлю свой телеграм канал. Периодически пишу туда про проекты, которыми занимаюсь (а их много, есть даже нейросети для театра). Более того, если вам вдруг нужен сайт или приложение с продвинутым бэкендом, то милости прошу, моя веб студия. Занимаемся проектами любой сложности.
Уважаемый Стив,
Привет от команды Bigtable. Мы хотим сообщить, что в дата-центре [название дата-центра] вы используете очень, очень старый бинарный файл Bigtable. Эта версия больше не поддерживается, и мы хотим помочь вам перейти на последнюю версию.
Пожалуйста, дайте знать, если вы можете запланировать некоторое время для совместной работы над этим вопросом.
Всего наилучшего,
Команда Bigtable
Уважаемый получатель,
Привет от какой-то команды. Мы хотим сообщить, что бла-бла-бла-бла-бла. Бла-бла-бла-бла-бла-бла, и бла-бла-бла немедленно.
Пожалуйста, дайте нам знать, если вы можете запланировать часть своего драгоценного времени на бла-бла-бла.
Всего наилучшего,
Какая-то команда
Уважаемый пользователь Google Cloud,
Напоминаем, что мы прекращаем обслуживание сервиса [важный сервис, который вы используете] с августа 2020 года, после чего вы не сможете обновить свои инстансы. Рекомендуем перейти на последнюю версию, которая проходит бета-тестирование, не имеет никакой документации, никакого пути миграции и которая заранее устарела с нашей любезной помощью.
Мы стремимся к тому, чтобы это изменение минимально повлияло на всех пользователей платформы Google Cloud.
Лучшие друзья навсегда,
Облачная платформа Google
Уважаемый получатель,
Пошёл ты к чёрту. Пошёл ты, пошёл ты, пошёл ты. Отбрось всё, что ты делаешь, потому что это не важно. Что важно, так это наше время. Мы тратим время и деньги, чтобы поддерживать наше дерьмо, и мы устали от этого, поэтому больше не будем его поддерживать. Так что бросай свои грёбаные планы и начинай копаться в нашей дерьмовой документации, выпрашивая объедки на форумах, и, кстати, наше новое дерьмо полностью отличается от старого дерьма, потому что мы довольно сильно испортили этот дизайн, хех, но это твоя проблема, а не наша.
Мы по-прежнему прикладываем усилия, чтобы все твои разработки стали непригодны для использования в течение одного года.
Пожалуйста иди нах,
Облачная платформа Google
Уважаемый получатель,
Мы хотели бы вам напомнить, что поддержка Python 2 устарела, так что пошёёёёёёёёл тыыыыыыы
и так далее. Круг жизни.
Заголовок конечно громковат, может не убивает, но уменьшит им доходы точно. Давайте кратко посмотрим что представила Apple на WWDC 2021, что такое Xcode Cloud?
Xcode Cloud - это сервис CI/CD, встроенный в Xcode и разработанный специально для разработчиков Apple. Он ускоряет разработку и доставку приложений, объединяя облачные инструменты, которые помогают создавать приложения, параллельно запускать автоматические тесты, доставлять приложения тестировщикам, а также просматривать отзывы пользователей и управлять ими.
Цикл разработки по мнению Apple заключается в этапах 1) Написать код 2) протестировать его 3) Интегрировать (в текущий) 4) Доставить до пользователя 5) Улучшить, и по новой. На то он и цикл. В принципе похоже на правду, так оно и есть.
Если вы хоть раз настраивали CI/CD для iOS приложений, вы знаете примерно какие там шаги, ничего сложного, но это может включать в себя использование нескольких сервисов, генерации сертификатов и тд и тп.
Теперь же Apple предлагает нам сделать это все не выходя из Xcode, давайте взглянем на процесс.
Для начала нам нужно настроить первый workflow, а потом уже который будет пробегать при PR/MR (pull request/merge request) на main/develop ветку в системе контроля версий.
I CI/CD
1) Жмем в новом Xcode при подключенном сервисе Xcode Cloud кнопку "создать workflow" и видим настройки
Name - название воркфлоу, Start condition когда запускать воркфлоу (например при изменении в main ветке), Environment - можно выбрать стабильную версию Xcode или новую бета версию, Actions - что собственно надо сделать, обычно выполнить archive и опубликовать например в TestFlight, после прогонки тестов, Post-Actions - что сделать после того как воркфлоу пройден, например написать в slack/telegram канал об этом событии
2) Выбираем репозиторий где хранится наш код
3) Выбираем с какой ветки собрать билд (при первой настройке)
Собственно готово, теперь можем посмотреть как выглядит в Xcode место где можно управлять сборками, запускать их, пересобирать и тд
Давайте теперь посмотрим как выглядит управлени воркфлоу (выше показан путь настройки первой сборки)
1) Выбираем "управление воркфлоу"
2) Выбираем настройки (например при pull/merge request что-то выполнять)
3) Выбираем какие тесты мы хотим прогнать в воркфлоу (UI или Unit тесты), я так понимаю речь именно про нативные тесты, про Appium и тд, пока ничего не известно.
4) И выбираем отправить сообщение в Slack после того как воркфлоу пройден
5) Готово
II Тесты
1) Давайте посмотрим как выглядит интерфейс работы с тестами, мы видим тут тесты которые пройдены при сборке а также устройства на которых они прогонялись
2) Посмотрим какие конкретно тесты прогнались на iPad Air, видим что тест кейс с Light mode, портретный режим, с Английским языком, далее видим какие конкретно тесты пройдены
3) Ну и совсем чудеса, можно смотреть скриншоты пройденных тестов
4) Можно также посмотреть какой тест упал, можно также пометить тест как Flaky (Флаки тест или другими словами тест неактуальный, который надо либо удалить либо переписать), для этого используется XCTExpectFailure (что в переводе логично видно по названию метода ожидаемый фейл)
Удобно.
III - Работа с системой контроля версий (и переписка прямо в коде в Xcode)
1) Изменения теперь видно еще нагляднее (привет всем кто пользуется визуальными штуками, а не через консоль при работе с git). Сверху мы видим наши локальные изменения (которые мы накодили) а снизу "висящие" pr/mr реквесты, которые можно посмотреть, и дать свой комментарий или approve (одобрение на слияние кода)
2) Даже видно какой тест план для этой фичи, которая просится в главную ветку
3) Переписка,комменты прямо в Xcode при pr/mr (а не на веб мордах gitlab/github/bitbucket и тд)
В общем очень круто и удобно
IV - Улучшения (Crashes/Сбои/Ошибки)
1) Все краши/сбои теперь видно прямо в Xcode (а не в веб морде Firebase или Sentry), код приходит сразу символизированный (symbolized log), то есть человекопонятный с указанием что и как произошло
2) А тестер (возможно и пользователь) может оставить комментарий при краше который вы сможете прочитать (и даже ответить!)
3) Ну и самое интересное вы сможете кликнуть открыть место краша в проекте
4) И вас за ручку проведут к вашему куску кода который натворил зло
Послесловие
Плюс ко всему это обещает все генерить сертификаты которые нужно автоматически, обновлять провижн (provision) файл тоже автоматически, судя по всему даже UDID теперь не надо будет собирать с инженеров по тестированию и заинтересованных менеджеров которые хотят смотреть на билд.
В общем как по мне сервис выглядит очень интересно, но пока недоступен для всех. Понятно что видимо он еще в стадии разработки, но как все задумывается, это выглядит круто, и очень упростит разработку для iOS.
Можете подать заявку на бета-тест здесь https://developer.apple.com/xcode-cloud/
Сколько он будет стоить пока тоже неизвестно.
И пока непонятно что с Android потому что обычно сервисы CI/CD используют сразу для двух платформ, так как приложения обычно тоже для двух платформ разрабатывают. Но может быть когда нибудь приложения для Android можно будет писать и в Xcode))
Изображение и информацию брал из видеосессий WWDC 2021, кому интересно как это выглядит вот видео про Xcode Cloud https://developer.apple.com/videos/play/wwdc2021/102/
Хотелось ли вам иметь несколько версий одного приложения?
Чтобы одной командой вы могли собрать приложение под определенное окружение?
Сталкивались ли вы с тем, что одновременно нельзя было установить несколько версий одного приложения на одном устройстве?
Всем привет!
Меня зовут Андрей!
И в этой статье я расскажу, как настроить сборку приложения для разных окружений.
Сразу отмечу, что слова версия, окружение и флейвор (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:
Мы настроим сборку приложения для двух окружений: DEVELOPMENT и PRODUCTION.
У каждой версии будут свои
иконки
наименования
application ID
переменные окружения, т.к. адрес к API серверу
Firebase проекты
Начнём...
Для начала создадим наш новый флаттер проект и мигрируем его сразу на null safety
$ flutter create flutter_starter_app$ cd flutter_starter_app && dart migrate --apply-changes
Откроем проект в любимом IDE.
Первым делом настроим переменные окружения для нашего проекта.
Эти переменные я предпочитаю хранить в файле
assets/.env
. И в зависимости какую версию приложения
мы собираем, мы указываем в этом файле соответствующие переменные.
Изменять этот файл будем в CI/CD (Continuous integration &
continuous delivery) в следующих статьях, а пока укажем значения в
этом файле один раз и продолжим.
# assets/.envENVIRONMENT=devAPI_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 настройка очень простая. Достаточно указать
следующие параметры в 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 нет такого понятия как 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
Результат: установилось два приложения с разными наименованиями.
Мы воспользуемся плагином flutter_launcher_icons, который сгенерирует для нас иконки для каждой платформы и для каждой версии по отдельности.
dev_dependencies: # ... flutter_launcher_icons: ^0.8.1
Добавим в корне проекта файлы конфигурации для этого плагина под каждую версию, в которых укажем какие картинки брать для генерации иконок.
# flutter_launcher_icons-dev.yamlflutter_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.yamlflutter_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 проекта под каждую версию через 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 =~ -([^-]*)$ ]]; thenenvironment=${BASH_REMATCH[1]}fiecho $environment# Name and path of the resource we're copyingGOOGLESERVICE_INFO_PLIST=GoogleService-Info.plistGOOGLESERVICE_INFO_FILE=${PROJECT_DIR}/config/${environment}/${GOOGLESERVICE_INFO_PLIST}# Make sure GoogleService-Info.plist existsecho "Looking for ${GOOGLESERVICE_INFO_PLIST} in ${GOOGLESERVICE_INFO_FILE}"if [ ! -f $GOOGLESERVICE_INFO_FILE ]thenecho "No GoogleService-Info.plist found. Please ensure it's in the proper directory."exit 1fi# 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 filePLIST_DESTINATION=${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.appecho "Will copy ${GOOGLESERVICE_INFO_PLIST} to final destination: ${PLIST_DESTINATION}"# Copy over the prod GoogleService-Info.plist for Release buildscp "${GOOGLESERVICE_INFO_FILE}" "${PLIST_DESTINATION}"
Называем эту Build Phase
понятным именем и
перемещаем ее немного выше:
Не забудьте поместить GoogleService-Info.plist
в
.gitignore
Запускаем приложение и видим результат.
Настройка Firebase на Android
Первое добавим зависимость для плагина google
services
в android/build.gradle
# android/build.gradlebuildscript { 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!
$ (0.06 / 100,000) * 116,000,000,000 = $ 69,600
60 * 1000 * 2.5 * 60 = 9 000 000 запросов в минуту
Google, моя любимая технологическая компания, это не просто отличная компания для работы. Это также отличная компания для сотрудничества. Инструменты Google очень удобны для разработчиков, имеют отличную документацию (по большей части) и постоянно расширяются.