Flutter в продакшене: Что мы узнали после выпуска 3 кроссплатформенных приложений
Flutter — наш основной выбор для кроссплатформенных приложений. За прошлый год мы запустили три production Flutter приложения — финтех-приложение, платформу для онлайн-образования и инструмент для полевого обслуживания. Каждое из них научило нас чему-то новому о том, что Flutter делает хорошо, и где нужно быть осторожным. Вот настоящие уроки, а не маркетинговая речь.
Управление состоянием: мы остановились на Riverpod
После того как мы попробовали Provider, BLoC и Riverpod в разных проектах, мы стандартизировали все новые Flutter работы на Riverpod. Причина проста: он обрабатывает внедрение зависимостей и управление состоянием в единой паттерне, он безопасен на этапе компиляции (без ошибок времени выполнения из-за отсутствующих провайдеров) и работает идентично в тестах.
BLoC мощный, но чрезмерно формален для большинства приложений. Паттерн событие-состояние добавляет boilerplate, который замедляет разработку без пропорциональных преимуществ для типичных CRUD и приложений, управляемых API. Мы используем BLoC для сложных stateful функций, таких как редактирование в реальном времени в сотрудничестве, где явный поток событий действительно полезен.
Наша архитектура Riverpod следует трёхуровневому паттерну: UI виджеты зависят от контроллеров (StateNotifier или AsyncNotifier), контроллеры зависят от репозиториев, и репозитории зависят от источников данных (API клиенты, локальное хранилище). Каждый уровень независимо тестируется и граф зависимостей явный.
Производительность: правило 90%
Flutter даёт вам 60fps из коробки для 90% экранов. Остальные 10% — длинные прокручиваемые списки, сложные анимации наложенные на прямые трансляции данных и экраны со множеством одновременных сетевых изображений — требуют целенаправленной оптимизации. Наиболее эффективные оптимизации, которые мы нашли:
Используйте const конструкторы везде, где это возможно. Эта единственная практика предотвращает ненужные перестройки виджетов и является самым лёгким выигрышем производительности во Flutter. Мы применяем её через lint правила. Используйте ListView.builder вместо ListView для любого списка длиннее 20 элементов — он лениво создаёт только видимые элементы. Кэшируйте сетевые изображения с cached_network_image и установите соответствующие лимиты памяти. Для сложных анимаций используйте RepaintBoundary для изоляции дорогостоящих операций рисования.
Оверлей производительности Flutter DevTools — ваш лучший помощник. Мы запускаем его на каждом экране во время разработки и отмечаем любой кадр, который занимает более 16 мс. Большинство проблем с производительностью возникают из-за thrashing-а макета (глубоко вложенные виджеты, которые вызывают несколько проходов макета) или ненужных перестроений (провайдеры, которые уведомляют слушателей слишком широко).
Каналы платформы: связь с нативным кодом
Каждому приложению Flutter в конечном итоге нужен код для конкретной платформы. Для нашего финтех-приложения это была биометрическая аутентификация и защищённое хранилище. Для нашего приложения для образования это была обработка push-уведомлений с пользовательскими полезными нагрузками. Для нашего приложения для выездного обслуживания это было отслеживание местоположения в фоновом режиме.
Мы используем Pigeon для типобезопасных каналов платформы вместо сырых MethodChannels. Pigeon генерирует шаблонный код для обеих сторон (Dart и нативной) из одного определения интерфейса. Это исключает сопоставление методов на основе строк, которое вызывает ошибки во время выполнения, и делает код каналов платформы по-настоящему поддерживаемым.
Наше правило: если существует плагин с оценкой 90%+ на pub.dev и активным обслуживанием, используйте его. Если нет, напишите тонкую оболочку канала платформы, которая делегирует работу нативному API. Никогда не боритесь с платформой — обнимайте её через чистые мосты.
Стратегия тестирования, которая действительно работает
Инструменты тестирования Flutter отличные, но недостаточно используются. Наша пирамида тестирования: 60% модульных тестов (хранилища, контроллеры, бизнес-логика), 30% тестов виджетов (отрисовка компонентов и взаимодействие), 10% интеграционных тестов (критические пользовательские потоки от начала до конца). Это соотношение даёт нам уверенность в рефакторинге без хрупкости слишком большого количества интеграционных тестов.
Функция тестирования файлов-образцов недооценена. Для экранов со сложными макетами мы снимаем снимок отрисованного виджета и сравниваем его с эталонным изображением. Это выявляет визуальные регрессии, которые пропускают модульные тесты. Мы запускаем тесты файлов-образцов на CI для каждого pull request.
Выбрали бы мы Flutter снова?
Да, с оговорками. Flutter — лучший кроссплатформенный фреймворк на сегодняшний день для приложений, которым нужны и iOS, и Android с общей кодовой базой. Язык Dart производителен, а система виджетов действительно хорошо спроектирована. Hot reload делает итерацию быстрой.
Но это не правильный выбор для всего. Приложения, состоящие на 50%+ из платформоспецифичного кода (тяжелый AR, сложные конвейеры камер, глубокая интеграция с ОС), должны быть нативными. Приложения, которые в основном отображают контент с минимальной интерактивностью, могли бы использовать React Native или даже PWA. Flutter сияет для интерактивных, управляемых данными приложений с умеренными потребностями в интеграции платформ — что описывает большинство стартап-продуктов.