Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Если вы только думаете начать писать свой первый Flutter-проект, скорее всего, вы даже не догадываетесь обо всех граблях, которые скрыты под листвой показательно низкого порога вхождения в технологию.
Наверняка вопросов больше, чем ответов. Стоит ли задумываться о локализации на старте и какой подход выбрать? А ничего страшного, что я Android совершенно не знаю? Если тестировщик просит несколько конфигураций сборки для него настроить, мне сразу резюме обновлять или не всё так страшно? Ещё и Dart этот… вроде обычный язык, но всё какие-то флешбеки из динамически типизированного прошлого простреливают.
Эта статья будет фонариком в тёмной и неисследованной перещере ужасов под названием «первый проект на Flutter».
Меня зовут Евгений Сатуров, и я руководитель отдела Flutter-разработки в Surf. Мы три года занимаемся Flutter-разработкой и за это время успели набить столько шишек, что даже страшно вспомнить. А ещё больший страх меня обуревает, когда я думаю о том, какое большое количество людей сейчас находятся в той же самой позиции, что и мы три года назад: только готовятся использовать Flutter в своем следующем большом, среднем или маленьком проекте.
Я постарался собрать все самые ценные советы в статье. Это — текстовая версия моего доклада. Если вам удобнее смотреть, чем читать, посмотрите видео.
Видео доклада «Стелем мягкую соломку на жёсткий Flutter» >>
Первые шаги
Получить базовые знания об Andriod и iOS
Flutter поддерживает шесть платформ, но в первую очередь пока остаётся фреймворком для разработки мобильных приложений под Android и iOS.
Для начинающих разработчиков это частая проблема: когда они берутся изучать Flutter и начинают с очевидного — с Flutter. Я советую начинать не с Flutter, а с основ Android и iOS. Звучит контринтуитивно, но их всё равно нужно будет изучать. Только в процессе разработки реального продукта это будет более стрессово и болезненно.
Минимум знаний, которые я рекомендую иметь о нативных платформах:
Представлять структуру проектов Android и iOS: из чего состоят, какие есть слои.
Хорошо знать синтаксис языков Kotlin и Swift. С Objective-C приходится сталкиваться гораздо реже, с Java — и того реже.
Понимать принцип работы ключевых фич платформы: например, permission — его нужно запрашивать очень часто. Существуют готовые плагины, которые берут много на себя. Но специфику и различия между платформами нужно понимать всё равно: они влияют на поведение приложения.
Понимать аспект работы приложения в фоне. Когда приложение работает в фоне, оно потребляет батарейку и вычислительную мощность устройства. Всё это влияет на негативный user experience пользователя от устройства.
Вендоры с каждым обновлением операционной системы ограничивают возможности приложений по работе в фоне. Запретить приложениям работать в фоне нельзя, поэтому разработчики ОС и вендоры придумали другие ограничения. Нужно понимать, как это отражается на нас: некоторые задачи, которые может потребовать бизнес, могут и вовсе быть нереализуемы. Это влияет на подходы к разработке проекта. На разных платформах — свои нюансы.
Помнить про push-уведомления. Как правило, все пользуются Firebasе для доставки push-уведомлений. Он берёт основную сложность этой задачи на себя. Тем не менее, я встречал ситуации — и не один раз — когда push-уведомления на Android приходят успешно, а на iOS — нет. Самая банальная причина этого — забыли запросить permission на получение push-уведомлений в приложении. Для iOS он необходим, а для Android вообще не нужен: там такого permission просто не существует.
Изучить все коробочные виджеты
Не факт, что этот совет для вас актуален. Возможно, вы прекрасно ориентируетесь в семействе виджетов. Но если нет, настоятельно рекомендую целенаправленно посмотреть, как они устроены, поделать с ними сэмплы.
Сомневаюсь, что кто-то всерьёз садился, открывал каталог виджетов и по одному их перебирал: изучал возможности, пробовал в действии, изучал API. Тем не менее, знать виджеты полезно. Иногда мы пытаемся решить задачу с помощью монстра из виджетов. А потом оказывается, что существует виджет из коробки, который решает задачу гораздо проще.
Классификации виджетов:
Простые и часто используемые виджеты: например, Expanded, Flex, Wrap. Они простые и чем-то могут напоминать друг друга. Поэтому люди иногда путают их между собой. Приходится уточнять, лезть в документацию.
Простые виджеты узкого применения. Яркий представитель — виджет Divider. Если использовать обычный контейнер высотой в 1–2 пикселя, покрашенный в серый или другой цвет, получается тот же самый Divider. И вроде бы для проекта это не фатальная разница: Divider или контейнер.
На самом деле с помощью Divider вёрстка становится более декларативной: он отделяет один блок от другого не только визуально в интерфейсе, но и описание одного блока от другого в коде. Получается, использовать Divider реально важно.
Сложные виджеты разметки, например, CustomMultiChildLayout. Мы привыкаем собирать UI из простых виджетов настолько, что не особо горим желанием разбираться в сложных. А это неправильно, так как порой порождает неоптимальный код.
Редко используемые виджеты разметки: IntrinsicHeight, OverflowBox, FittedBox. Если не знаешь про их существование, естественно, в голову не придёт их использовать.
Sliver-виджеты. Sliver-виджеты в основном выступают как адаптеры для других виджетов, которые можно использовать в Sliver-окружении. Нужно разобраться в первую очередь, как работает вообще вся эта тема со Sliver. Это облегчит жизнь, когда нужно будет решить сложную задачку по реализации классного дизайна.
Подробнее про Sliver-виджеты писал мой коллега Михаил Зотьев >>
Понимать, как работают constraint
Constraint — определяющая вещь в вопросах верстки пользовательского интерфейса. По мнению Flutter-разработчиков, эта вещь наиболее неинтуитивно реализована во Flutter: много различий относительно принципов, которыми мы привыкли руководствоваться в императивных UI-фреймворках.
Ситуация: есть контейнер, которому задаются фиксированная высота, ширина и цвет. Из всех свойств применяется только цвет. Высота и ширина — нет, потому что контейнер растягивается на весь экран или на всё доступное место в родительском виджете.
Почему такое может быть? Это не баг — это нормальное поведение. Ему посвящена огромная страница в документации Flutter, которая называется Understanding constraints. В ней есть более 30 примеров различных комбинаций виджетов, каждый из которых раскрывает какой-то отдельный нюанс работы constraint при верстке. Их важно изучить: это сильно поможет верстать макеты.
Старт проекта
Не всё можно отложить на потом: некоторые вещи нужно делать сразу. Либо потом их нельзя будет сделать вовсе, либо это будет стоить несоизмеримых затрат времени, денег, ресурсов, нервов и в целом не очень хорошо скажется на качестве продукта. В этом блоке я постарался собрать советы, которые помогут сделать всё важное на старте сразу.
Настроить релизную сборку
Не откладывайте это до момента, когда релиз на носу: нужно будет фиксить кучу багов, а тут ещё и релизные сборки придётся настраивать.
Android-сборку настроить несложно: может появиться ложное ощущение что всё так же просто будет и для iOS. И вот тут начинается самое веселое: чтобы понять, как это всё сделать в iOS, нужно гораздо больше времени. Provisioning профайлы, сертификаты, куда прописать девайсы и нужно ли их прописывать, как всё это связать с консолью, как в итоге задеплоить артефакты, как их собрать правильно.
Без Xcode, конечно же, не обойтись. Придётся и в его замечательном интерфейсе разобраться и посмотреть, как всё настраивать. Поэтому первое, что нужно делать при старте проекта, – это заранее всё это настроить.
Есть хорошая статья, почему Apple реализовали подписание приложений именно таким образом: когда я её прочитал, мне много стало понятнее. Советую и вам поискать просветления в ней.
Статья Debugging how you think about code signing >>
Настроить CI/CD
В том, что касается CI/CD, люди делятся на два противоборствующих лагеря. Первый — те, кто работает в больших компаниях. Там наверняка есть DevOps-отделы, которые занимаются настройкой CI/CD. Разработчик просто говорит: «Я хочу CI/CD», — и через пару дней у него есть CI/CD со всеми необходимыми настройками и степами, которые нужны для разработки.
Другой лагерь – это люди, которые работают в маленьком стартапе: два разработчика, никаких DevOps. В общем, что сам сделаешь, то и будет. Естественно, никто отдельного времени на настройку CI/CD не выделит.
Хорошая новость: простенький CI/CD можно развернуть буквально на коленке. Для этого потребуется Github Actions. Удобная и полезная штука, которая сильно снижает порог вхождения в тему.
Можно развернуть её, написать простенький пайплайн, ничего не зная про настройку CI/CD, и реализовать необходимые шаги. Особенно рекомендую сконцентрироваться на сборке: она даст понимание, работает ли ваш код или нет. Если сборка упала, значит, скорее всего, в коде что-то сломано.
Также не помешают шаги с прогоном тестов и форматированием. Но это опционально: например, если в вашем проекте нет тестов, шаг с прогоном становится совершенно бессмысленным. Если вы считаете, что перфекционизм при форматировании – это лишнее, шаг можно исключить. Но я крайне рекомендую его всё-таки оставить.
Если хотите освоить Github Actions, рекомендую начать со статьи «Используем бесплатные возможности Github Actions для CI/CD на Flutter-проекте». Ключевой момент: даже если проект находится в приватном репозитории и Github Actions платный, затраты можно минимизировать. Нужно развернуть собственный self-hosted runner и подключить его через специальную софтину к Github Actions: появится возможность собирать сборки сколько угодно до тех пор, пока машина находится в онлайне.
Предусмотреть локализацию
Даже если приложение моноязычное и никаких предпосылок для других языков нет, рекомендую всё равно заложить механизм локализации. На старте это не займёт много времени. А вот переписать приложение, в котором локализацию не предусмотрели, практически невозможно.
Если закладываете механизм локализации на старте проекта, у вас появится дополнительный ограничитель, который помешает реализовывать работу со строками на том архитектурном уровне, на котором это не должно происходить. Работать со строками нужно строго: на слое UI либо на презентационном слое. Независимо от выбранной архитектуры, слой бизнес-логики — явно не то место, где нужно работать со строковыми ресурсами.
Для локализации я рекомендую intl. Если intl пугает своей монструозностью, обилием бойлерплейт-кода, который необходимо писать, вам помогут плагины для Android Studio и VS Code. Они будут генерировать весь этот ужас сами, и работать с локализацией станет немного приятнее.
Сконфигурировать несколько типов сборок
Настройка различных конфигураций сборок — мощный инструмент, которым редко пользуются. А между тем, он сильно ускоряет процесс: можно избавить себя от лишних пересборок, выделив изменяемые настройки на другой уровень реализации. Некоторые настройки конфигурации мы даже можем изменять «на лету» — без пересборки приложения. Для решения этой задачи у нас есть богатый арсенал.
Flavors и схемы в Android и iOS. Это то, что имеет отношение непосредственно к нативной платформе, на которой собираем приложение. Bundle ID, название приложения, иконка — всё это конфигурируется через flavors и схемы, ведь на эти настройки самой сборки больше повлиять никак нельзя. Мы заранее конфигурируем несколько типов сборок с разными настройками.
Точка входа — замечательная особенность, которая есть у Dart-приложений. Её можно подменять при сборке: создать несколько main-файлов, и собирать сборки с использованием разных main-файлов. Это даёт богатые возможности для настроек деталей реализации: в main-файлах можно держать глобальную конфигурация, которая определяет, например, к серверу с каким URL обращается сборка.
Тестировщики любят, когда у них на выбор есть несколько сборок, которые обращаются к разным серверам: так не придётся собирать каждый раз вручную сборку с нужным сервером. Наличие таких сборок, сконфигурированных при помощи разных main-файлов, сильно выручает. Кроме того, если в проекте есть feature toggle, здесь их тоже можно настраивать.
--dart-define — команда, через которую можно передавать переменные окружения. Мы в Surf её не используем — так исторически сложилось. Я знаю, что есть приверженцы, которые --dart-define
любят, но мы решаем всё подменой main-файлов и не испытываем никаких проблем.
Тема и стилизация
Настройки стилизации захватывают ещё больше отдельных частей приложения, чем локализация и строковые ресурсы. Поэтому тему и стилизацию стоит либо делать сразу правильно, либо вообще отказаться от работы с темой, настраивая внешний вид виджетов «на местах».
Представьте: практически каждый виджет имеет настройки — цвета, закругления углов, тени, текстовые стили и так далее. Если нужно будет сделать глобальный редизайн, то из простой задачи смены одного свойства в одном месте в теме приложения она превращается в задачу на недели: придётся ходить и искать свойства по всему приложению.
Но вместе с тем есть несколько ложек дёгтя. Во-первых, работать с темой нужно аккуратно. Некоторые свойства темы могут отвечать за неожиданные вещи: например, canvasColor. Мы столкнулись в своё время с багом: контекстное меню, которое вызывается при выделении текста в текстовом поле, нечитабельно, потому что черный текст отображается на тёмно-сером фоне.
Долго бились с этой проблемой. В итоге я залез в исходники контекстного меню и обратил внимание, что бэкграунд пробрасывается туда через много уровней из свойства canvasColor-темы. А тёмный canvasColor был прописан в теме, которая применялась глобально для всего приложения: мы так реализовали тёмную тему. Это решение оказалось неправильным.
Подобные вещи вы можете найти в избытке во Flutter-фреймворке. Не рекомендую менять свойства темы, если не уверены, что свойство отвечает только за то, что вам нужно. Саму стилизацию рекомендую выполнять либо в теме, либо в теле переиспользуемого компонента — но крайне желательно обращаться при этом к свойствам темы.
Но если честно, здесь всё зависит не только от вас. Если дизайнеры не работают в рамках дизайн-системы и не делают UI kit, они могут «сломать» в макетах консистентность компонентов, которую сами же и создали. И все красиво настроенные темы пойдут прахом: придётся хардкодить стилевые настройки прямо на местах. Поэтому советую сделать всё, что от вас зависит, чтобы в проекте такой UI kit появился.
И если UI Kit всё-таки появился, рекомендую обратить внимание на опенсорсный сервис Widgetbook.io. Он нужен, чтобы получить наглядное и красивое представление всех виджетов экранов, которые есть в приложении. Я думаю, что для большого проекта, в котором переиспользованных компонентов великое множество, это будет очень хорошим подспорьем для онбординга новых членов команды.
ScreenUtil рекомендую использовать, если ориентируетесь на девайсы с разным форм-фактором и не хотите тратить особенно много времени на реализацию адаптивности.
ScreenUtil позволяет получать адаптивность практически «бесплатно». Указываете нефиксированное значение ширины и высоты через специальный extension. Он масштабирует указанные значения и делает их относительными к размеру девайса, на котором приложение запущено. ScreenUtil классно себя показывает как быстрое решение для получения достаточно хорошего результата.
Продуктивность
Debug-экран стал большим подспорьем в коммуникациях с отделом тестирования. Он содержит фичи, которыми пользуются в первую очередь тестировщики. Например, мы позволяем сменять URL приложения на лету: достаточно зайти на debug-экран, выбрать другой URL, перезапустить приложение, и оно уже будет обращаться к другому серверу. Даже не нужно ставить другую сборку: это экономит массу времени тестировщику.
Кроме того, на этом экране можно настраивать прокси. Это нужно, чтобы:
Дать возможность подключаться к инструментам для подмены ответов и имитации поведения сервера — например, Charles. Тестировщики будут благодарны за такую возможность.
Использовать экран для демонстрации UI kit либо для демонстрации нотификаций, для включения и отключения feature toggle. Тут вы ограничены только вашей фантазией и фантазией тестировщиков.
Важный момент — не забыть этот экран выпилить из продовой сборки. Желательно сразу написать его таким образом, чтобы точка входа была недоступна в продовой сборке: реальным пользователям этот экран ни к чему.
В нашем случае debug-экран выглядит вот таким образом.
От приложения к приложению он может немножко отличаться — в зависимости от специфики проекта. Но в вашем случае вы тоже можете его реализовать как-то по-своему. Просто сама идея мне кажется очень любопытной и почему-то недооцененной.
Навигация
Во Flutter навигация неплохо реализована из коробки: можно взять самый обычный императивный навигатор и написать на нём большой проект.
Мы долгое время так и делали. Но за три года работы с Flutter мы познали такую мудрость: если долго писать приложение, когда-нибудь обязательно прилетит задача на реализацию дип-линков. И вот здесь вы вспомните, что есть же ещё и другие способы реализовать навигацию, на которых можно было бы дип-линки сейчас буквально в две строчки поддержать.
Если такая мысль посетила и вас, возможно, вам нужно посмотреть либо в сторону Navigator 2, хотя о нём разные отзывы, либо в сторону таких решений, как go_router, который сейчас активно продвигает Крис Селлс из Google (UPD: к сожалению, недавно Крис покинул компанию).
Есть несколько плагинов из числа популярных, которые я могу посоветовать, — в частности, для VS Code.
Better comments — выделяет цветом важные комментарии в коде: есть предопределённые стили для стандартных комментариев, но можно настроить собственные.
Color Highlight — в исходном коде находит строки, содержащие закодированные цвета, и выделяет эти строки указанным цветом.
Rainbow Brackets — подсвечивает пары скобок: у каждой пары свой цвет. Незакрытые скобки подсвечивает красным.
Pubspec assist — позволяет, не выходя из редактора, добавлять зависимости в pubspec.yaml в проектах на Dart или Flutter.
Если плагина, который вам нужен, не существует, – напишите его. А если у вас нет идеи сейчас для своих собственных плагинов, то я могу поделиться самой банальной идеей из всех, которая возникает у каждого. Это плагины для генерации шаблонного кода, который вы пишете очень часто.
Если вы выбрали какую-то определённую архитектуру для своего проекта, скорее всего, каждый раз, когда вы создаете новый экран, вы создаете целую иерархию классов для этого экрана. Как правило, это похоже на копипаст. И только потом вы начинаете вносить туда изменения. Генерацию этих скелеты для экранов вполне можно автоматизировать через собственные плагины, чем мы сейчас в нашей команде и занимаемся.
Интересная особенность: с приходом Flutter for Web отладку теперь можно делать не только на реальном девайсе или эмуляторе, когда пишем под Android и под iOS, но и в вебе. Можно собрать приложение, запустить его в браузере, подогнать его под размеры девайса и пользоваться им для отладки.
Здесь, конечно, масса допущений: в проекте не должно быть библиотек, которые не поддерживаются вебом, и вообще не должно быть фич, которые ломают поведение приложения в вебе. В сложных проектах, скорее всего, это применить не получится. Но если у вас есть что-то супер простое, то вполне можете попробовать.
UPD: А ещё лучше вместо эмулятора подходит для отладки ваших приложений Flutter for Desktop. Собирайте ваше приложение так, словно оно предназначается для работы на десктопе и отлаживайте без задержек и проблем. Не забывайте о том, что платформенные интеграции с мобильными платформами таким образом, конечно, не протестировать.
Генерация сетевого слоя. Swagger и OpenAPI — открытые протоколы, которые имеют достаточно большую свободу реализации. Это можно заметить, попробовав в действии валидаторы OpenAPI, которые в большом количестве есть в маркетплейсе, например, VS Code. Можно поставить 10 валидаторов, и все они найдут ошибки, которые другие не найдут.
Всё это скажется на попытке что-то сгенерировать: генераторы зачастую требуют определённый формат, в котором нужно вести документацию. От этого достаточно тяжело уйти.
И здесь я хотел бы прорекламировать наше решение SurfGen, которое мы сейчас активно развиваем: оно может стать тем рабочим генератором, который помогает в 100% случаев из 100. Но пока ещё не могу похвастаться его готовностью: он проходит у нас внутреннюю обкатку. Мы активно движемся в этом направлении.
Осторожно, Dart!
Миксины. Dart — замечательный язык, который за последние годы стал ещё лучше и имеет массу уникальных фич и возможностей. С другой стороны, у него, как и в любом другом языке, есть то, к чему лучше относиться с осторожностью: например, миксины. Когда с ними сталкиваешься в первый раз, они вызывают вау-эффект: «Ничего себе! Можно теперь ещё чуть ли не множественное наследование делать».
Хотя, конечно же, нужно понимать, что миксины – это не множественное наследование. Это просто небольшая фича, которая помогает увеличить процент переиспользуемого кода в проекте. С ней нужно обращаться аккуратно: если вы знаете, что будет напечатано в консоли после того, как будет исполнен этот код, я могу вас поздравить. Вы, как минимум, хорошо понимаете принцип действия миксинов.
Но сам факт, что IDE, анализаторы и компиляторы не помешают вам написать этот код, довольно опасен: можно получить странный и трудно отлавливаемый баг с поведением, если вы дублируете название переменных, методов в разных миксинах и применяете их к одному и тому же классу. В своё время я сам столкнулся с такой неприятной особенностью, после чего к миксинам отношусь с осторожностью.
Восклицательный знак и null safety. С переходом на null safety Dart стал другим языком. Но вместе с тем появился и способ выстрелить себе по ногам, причем достаточно соблазнительный: я говорю про восклицательный знак, который можно использовать для обращения к nullable переменной, игнорируя безопасность, которую даёт null safety.
Это достаточно неочевидная особенность Dart: если обращаться к глобальной переменной, которая имеет nullable-тип, даже проверка на null в предыдущей строке не даёт гарантии, что эта переменная в дальнейшем будет non null.
Обратиться к хвосту в строке tail.cut просто не получится. Компилятор не даст это сделать: вместо tail и переменной у нас может быть getter, который может неожиданно вернуть null даже при двух последовательных вызовах.
В этом случае возникает всегда такой соблазн. Ты говоришь: «Слушай, я же проверку сделал. Он точно не null. Это же не getter». Ставишь восклицательный знак, обращаешься к переменной и всё хорошо — до тех пор, пока кто-нибудь не придёт и эту переменную на getter не поменяет. Но когда это случится? Да и случится ли?
Такое отношение начинает распространяться на другие кейсы использования восклицательного знака, и ты начинаешь его расставлять повсеместно. И рано или поздно что-нибудь упадёт в runtime, а ещё хуже — на проде. Поэтому восклицательным знаком лучше не пользоваться.
Типизация
Отключайте implicit-casts и implicit-dynamic: это спасёт немало нервов при отладке. А с dynamic вообще лучше дела не иметь: сейчас уже почти не осталось ни одного кейса, когда dynamic действительно имеет смысл применять в работе.
Настраивайте статический анализатор максимально строго. Чем строже настройки, тем меньше случайных ошибок останутся незамеченными в кодовой базе. Нет действительно ни одной причины не сделать этого. Вместо того, чтобы мучиться с настройкой самому, вы можете воспользоваться нашими наработками. Пакет называется surf_lint_rules, доступен на pub.dev. Он очень строго настроен и даст вам настройки, которые мы применяем в работе каждый день.
Не закрывайте глаза на warning — предупреждения: даже если у вас будут самые строгие настройки, но все они будут предупреждениями, необязательными к исправлению, и вы на них внимания обращать на будете, то смысла никакого абсолютно от этих настроек не будет. Выработайте в команде warning-нетерпимость. И по-максимуму старайтесь warning пресекать.
RxDart
RxDart нужен только для сложных операций с потоками данных и синхронизации потоков данных. RxDart мы некоторое время по привычке затаскивали в проекты, потому что многие из нас – выходцы из Android. В Android RxJava — очень популярная штука. Раньше в Android не было нормального способа взаимодействовать с асинхронными операциями: много избыточного, запутанного бойлерплейт-кода. Хорошую реализацию написать было довольно трудно. RxJava вывел работу с асинхронными процессами на новый уровень.
Как оказалось, смысла в RxDart осталось примерно 25% от исходного. Dart из коробки обладает всеми необходимыми классами для работы с асинхронщиной: например, получением отложенного результата операций с помощью Future, стримами для работы с потоками данных. Если нужна взрослая и настоящая работа в фоне, то — Isolate.
И на RxDart у нас остается, пожалуй, только небольшой кусочек сложных операций с потоками данных, которые встречаются далеко не в каждом проекте: синхронизацией, сложной фильтрацией, маппингом и другими.
Качество кода
Несколько рекомендаций по организации проекта.
Структурируйте файлы проекта по фичам. Это самый удобный способ ориентироваться в проекте, который не является микроскопическим по масштабам.
Нейминг. Имя файла должно всегда соответствовать имени класса. Иначе некоторые классы вы будете искать в вашей кодовой базе очень и очень долго. Особенно если забудете точное написание их названия.
В одном файле — только один класс. Это неочевидное и очень полезное правило, которое сильно упрощает навигацию по проекту, — особенно для новых разработчиков в команде.
Если мы имеем дело с виджетами, я рекомендую этим правилом немного пренебречь, потому что виджет — это и виджет, и state. Это неразрывные вещи. Их можно декларировать в одном файле.
Не бойтесь доверять кодогенерации простые задачи. Страх перед кодогенерацией зачастую исходит из монструозных DI-фреймворков, которые едва ли не всю логику построения дерева зависимости для приложения берут на себя и решают с помощью кодогенерации.
Не имеет ни малейшего смысла делать вручную простые задачи. Кодогенерация для моделей данных, например, через json-serializable или через какие-то другие библиотеки – это то, что должно быть в вашем проекте по умолчанию.
Качество продукта
Оба совета касаются производительности, потому она влияет на user experience больше всего.
Первый совет: прогревать анимации. Больше года Flutter-сообщество страдало от проблемы с junk кадров при анимациях на iOS — и не только на iOS. Хорошо, что прошли те времена, когда не было никакого способа эту проблему пофиксить. Существовали разные костыльные варианты, но это не тянуло на фундаментальное решение.
Теперь такая возможность у нас есть, поэтому забывать о прогреве анимаций совершенно не стоит. Можно сделать её даже вручную, если приложение не покрыто интеграционными тестами. Хотя и интеграционные тесты для этой цели написать тоже не так страшно, как казалось бы. Поэтому не забывайте про прогрев анимаций: эффект действительно будет.
Второй совет: если анимации лагают, обратитесь к rebuild stats. Совет работает не всегда, но в моей практике он работал поразительно часто. Если у вас есть проблема с performance на каком-то экране, вы видите, что анимации лагают и не очень плавно работают, первый инструмент, к которому вам нужно обратиться, – это Rebuild stats. Посмотрите, какие виджеты перерисовываются сотни и тысячи раз — это явно больше, чем надо.
Если визуально всё нормально, рекомендую поскроллить экран и понажимать кнопки. Иногда перерисовки начинаются после какого-то действия: например, в коде есть неправильное зацикливание процессов, которые всякий раз триггерят перерисовку.
Если все виджеты работают адекватно и не перерисовываются чаще, чем надо, стоит идти в devtools и смотреть на всякие CPU-профайлеры.
Мы часто видим проекты, в которых не соблюдались советы, с которыми вы только что ознакомились. Часто такие проекты становятся разочарованием для команды. Команда винит технологию, то есть Flutter, в том, что он недостаточно хорош, недостаточно развит и не готов к использованию в крупном и сложном проекте.
Этой статьёй я хотел бы развенчать этот миф. Flutter готов к сложным вызовам. Но наломать дров в технологии, которая ещё не успела так плотно обрасти лучшими практиками, проще простого. Надеюсь, мои советы помогут вам получить максимум возможностей и бенефитов, которые может предоставить вам фреймворк Flutter.