В данной статье рассматривается архитектура приложения на Flutter с использованием паттерна BLoC и анализируется влияние такого подхода на производительность приложения. Проведены модельные тесты производительности (время отклика, потребление памяти) при различных сценариях использования приложения. Также обсуждаются вопросы масштабируемости кода с BLoC и сравнение с альтернативными подходами (например, Provider, MVVM).
Flutter — популярный фреймворк для разработки кроссплатформенных приложений, созданный Google. Он позволяет из единого кода получать нативно скомпилированные приложения под мобильные платформы, веб и настольные системы [1]. Высокая производительность Flutter, быстрый цикл разработки и выразительные средства создания интерфейса обусловили его широкое распространение [1].
В экосистеме Flutter существует множество подходов к управлению состоянием — от встроенного механизма setState для локального состояния виджетов до различных шаблонов и библиотек (Provider, BLoC, Redux, Riverpod и др.) [4]. Официальная документация Flutter перечисляет разные подходы и подчеркивает, что выбор зависит от требований приложения [4]. Среди этих подходов паттерн BLoC (Business Logic Component) выделяется как один из наиболее популярных и широко используемых в крупных приложениях.
Паттерн BLoC и его роль в архитектуре Flutter
BLoC (Business Logic Component) — это архитектурный шаблон, предназначенный для разделения логики приложения и UI-слоя. Идея паттерна заключается в вынесении всей бизнес-логики (работа с данными, обработка событий) в отдельные компоненты (BLoC-модули), изолированные от кода интерфейса. Тем самым достигаются лучшая модульность, тестируемость и поддерживаемость приложения [1]. Паттерн BLoC получил широкое распространение во Flutter-разработке, особенно в приложениях со сложной бизнес-логикой [1]. В основе BLoC лежат потоки событий и состояний: UI генерирует события (например, нажатия кнопок), которые передаются в BLoC, а BLoC, обработав бизнес-логику, выдает новое состояние, на которое подписан UI и которое приводит к обновлению экрана [1]. Такой однонаправленный поток данных (Event → BLoC → State) обеспечивает предсказуемое управление состоянием приложения и облегчает отладку.
Важно отметить, что BLoC-паттерн реализуется во Flutter обычно с помощью библиотеки flutter_bloc, которая предоставляет базовые классы для BLoC, а также виджеты для интеграции в интерфейс. В контексте общей архитектуры приложения использование BLoC способствует соблюдению принципа Separation of Concerns (разделения ответственности). Согласно руководству по архитектуре Flutter, приложение целесообразно делить на как минимум два слоя: слой интерфейса (UI layer) и слой данных (Data layer), каждый из которых решает свои задачи [2]. BLoC же можно рассматривать как часть промежуточного слоя бизнес-логики, своего рода «ViewModel» в паттерне MVVM, который обеспечивает связь между UI и данными. Официальная архитектура BLoC-ориентированных приложений предполагает разделение на три основных слоя: Presentation (представление), Business Logic (бизнес-логика) и Data (данные) [3]. В презентационном слое находятся виджеты интерфейса, в слое бизнес-логики — BLoC-модули, обрабатывающие события и формирующие состояния, а слой данных отвечает за работу с внешними источниками (репозитории, провайдеры данных) [3]. Такой подход улучшает модульность: UI-слой не зависит от деталей реализации логики, а BLoC-модули могут повторно использоваться и тестироваться изолированно от интерфейса.
BLoC-паттерн во многом перекликается с архитектурой MVVM (Model-View-ViewModel), широко применяемой в разработке приложений. Оба подхода нацелены на разделение UI и логики и тем самым имеют схожие принципы. Главное отличие в реализациях: во Flutter BLoC традиционно опирается на реактивные потоки (Stream) для обмена данными между UI и логикой, тогда как классический MVVM может использовать механизмы двунаправленного связывания или наблюдателей (например, LiveData в Android). Впрочем, современное руководство Flutter фактически рекомендует MVVM-структуру (View + ViewModel + Repository) для организации проекта [2], и реализация BLoC вписывается в эту парадигму: BLoC-компонент играет роль ViewModel, а репозитории выступают в роли Model-слоя. Таким образом, использование BLoC не противоречит общим архитектурным принципам Flutter, а лишь предлагает удобный шаблон их соблюдения.
Структура BLoC-модулей создание отдельных модулей для каждой функциональности, состоящих из классов событий, состояний и самого BLoC. Рассмотрим структурирование такого модуля на примере упрощенного сценария — авторизация пользователя. Допустим, требуется реализовать экран входа с формой ввода логина и пароля. В рамках BLoC-подхода мы сначала определяем набор событий, на которые будет реагировать логика: например, LoginButtonPressed (нажатие кнопки «Войти»), LogoutRequested (событие выхода) и т. д. Эти события оформляются как классы, наследующие абстрактный класс AuthEvent:
Аналогично, описываем возможные состояния процесса авторизации — например, initial (начальное), loading (выполняется вход), authenticated (пользователь успешно вошел), authenticationFailure (ошибка входа). Они реализуются как наследники абстрактного класса AuthState
Далее создается класс AuthBloc, наследующий от базового Bloc
<authevent, authstate="">
(предоставляется пакетом flutter_bloc). В конструкторе BLoC устанавливается начальное состояние (например, AuthInitial). Главная логика заключается в переопределении метода mapEventToState (в новых версиях используется API через методы on
В приведенном псевдокоде метод _onLogin отрабатывает событие нажатия кнопки входа: переводит состояние в «загрузка», вызывает метод репозитория для проверки учетных данных, и в зависимости от результата публикует состояние либо «аутентифицирован» (успех), либо «ошибка» (неуспех). Метод _onLogout обрабатывает событие выхода, вызывая соответствующую функцию репозитория и сбрасывая состояние к исходному. BLoC не занимается непосредственным отображением сообщений об ошибках или навигацией — он лишь меняет свое состояние, а уже пользовательский интерфейс, подписанный на это состояние, должен соответствующим образом отреагировать (например, показать экран с ошибкой или перейти на следующий экран при успешном входе).
Для подключения BLoC-модуля к UI используется виджет BlocProvider, который обычно размещают выше по дереву виджетов (например, оборачивают им материал-приложение или конкретный экран). Он создаёт экземпляр BLoC и делает его доступным дочерним виджетам.
Внутри экранов (виджетов), которым нужен доступ к BLoC, можно получить его через BlocProvider.of
Таким образом, структура BLoC-модуля включает четко выделенные компоненты: события (описывают «намерения» или действия пользователя), состояния (отражают текущее состояние интерфейса с точки зрения данных) и класс BLoC, который получает события и на их основе выдает новые состояния. На рис. 1 схематично показано взаимодействие между UI, BLoC и слоем данных приложения.
Рис. 1. Архитектура паттерна BLoC
Оценка производительности при использовании BLoC
Одним из ключевых критериев успеха архитектурного решения является влияние на производительность приложения. В контексте Flutter важно, чтобы дополнительный уровень абстракции (такой как BLoC) не приводил к заметным задержкам в отклике интерфейса или перерасходу ресурсов (памяти, CPU). Для оценки производительности паттерна BLoC были рассмотрены типичные сценарии работы мобильного приложения: переходы между экранами , загрузка данных с последующим обновлением UI , а также массовое обновление списка виджетов . В частности, опираясь на опубликованные сравнения state management-подходов [4], проведены измерения среднего времени отклика интерфейса и потребления памяти при использовании BLoC по сравнению с более простым подходом setState (без выделения слоя логики).
Для моделирования нагруженного сценария был разработан тест, в котором генерируется большое количество обновлений UI: например, имитация поступления 1000 и 10000 новых элементов списка, отображаемых на экране. В одном случае управление состоянием реализовано через BLoC, в другом — с помощью вызовов setState напрямую в виджетах. Результаты измерений приведены в табл. 1.
Таблица 1
Показатели производительности при обновлении списка из 1000 и 10000 элементов: сравнение подходов без BLoC и с паттерном BLoC
|
Метрика |
setState (1000) |
BLoC (1000) |
setState (10000) |
BLoC (10000) |
|
Загрузка CPU, % |
0,57 |
0,55 |
0,46 |
0,45 |
|
Протребление памяти, МБ |
7,28 |
6,69 |
25,34 |
23,27 |
|
Время выполлнения, с |
1,27 |
1,06 |
4,23 |
3,54 |
Как видно из табличных данных, использование BLoC-архитектуры не влечет существенных накладных расходов, а напротив, в ряде случаев позволяет улучшить показатели производительности по сравнению с наивным подходом. Так, при загрузке 10000 элементов суммарное время обработки событий и обновления интерфейса в приложении с BLoC на ~16 % меньше (3,54 с против 4,23 с), чем при использовании только setState. Потребление памяти в сценарии с 1000 и 10000 элементов снизилось примерно на 8 % при переходе на BLoC (например, 23,3 МБ против 25,3 МБ на 10000 элементов). Нагрузка на CPU в обоих вариантах оказалась низкой (менее 1 % от одного ядра) — разница между BLoC и обычным подходом составляет сотые доли процента [4], что находится в пределах погрешности. Таким образом, BLoC не создает заметного overhead по CPU даже при интенсивной генерации состояний.
Интересным наблюдением является то, что выигрыш BLoC в производительности проявляется сильнее при увеличении объема обрабатываемых данных. При сравнительно небольшом числе обновлений (1000) разница во времени и памяти между BLoC и setState минимальна, а в некоторых тестах реализация на чистом Flutter даже оказывалась чуть быстрее [4]. Это объясняется тем, что для простых случаев накладные расходы на создание объектов событий/состояний и обработку потоков могут быть относительно ощутимы. Однако с ростом нагрузки (10 тысяч обновлений) архитектурные преимущества BLoC становятся очевидны — он справляется с большим количеством изменений эффективнее, чем лобовой вызов setState на каждом элементе [4]. Последнее связано с тем, что при грамотной архитектуре можно избегать лишних перестроений виджетов. В исследовании отмечено, что версии приложения с BLoC и Provider генерировали меньше виджетов при обновлении интерфейса, чем аналог с чистым setState, благодаря чему затраты на сборку и рендеринг снижались [4]. Иными словами, BLoC способен локализовать обновления UI — изменяется только та часть интерфейса, которая подписана на поток состояний, вместо массового перестроения всего экрана.
Рассмотрим отдельно сценариий перехода между экранами. Здесь измерялось время, необходимое для отображения нового экрана после навигации, а также сохранность состояния. Использование BLoC позволяет вынести состояние экрана (например, содержимое формы или результаты предыдущих загрузок) за пределы виджета экрана — например, разместив BLoC выше по дереву (на уровне приложения) или используя механизм сохранения состояния (hydrated BLoC). В результате при возвращении на экран или повторном создании виджета данные могут быть загружены из уже существующего состояния BLoC, минуя повторные вызовы к API. Практические замеры показывают, что время перехода «туда и обратно» между экранами с сохранением состояния через BLoC ниже, чем при полной переработке данных при каждом открытии экрана. Экономия времени в подобных случаях составляет до сотен миллисекунд (зависит от объема данных), что улучшает восприятие производительности пользователем.
При загрузке данных (например, запрос к серверу) паттерн BLoC также демонстрирует удобство и не создает избыточных задержек. В традиционном подходе виджет при загрузке данных может вызывать setState несколько раз (для отображения индикатора загрузки, затем результата или ошибки). С BLoC же UI подписывается на состояния Loading/Success/Error, которые приходят асинхронно. Это позволяет, во-первых, не блокировать основной поток UI во время выполнения запроса (BLoC легко интегрируется с Future/async операциями), а во-вторых, разгружает виджет от лишней логики. Замеры отклика интерфейса при загрузке данных через BLoC показывают, что пользователь видит реакцию (появление индикатора) практически мгновенно (доли секунды), поскольку отправка события в BLoC и переключение состояния на Loading происходит синхронно на цикле обработки событий Flutter. Дальнейшая задержка обусловлена уже временем сетевого отклика, которое одинаково в обоих подходах. Таким образом, время до первой реакции UI при старте загрузки не увеличивается при использовании BLoC.
Важно подчеркнуть, что правильно реализованный BLoC не выполняет долгих вычислений на главном потоке. Если бизнес-логика включает тяжелые операции (парсинг больших данных, криптография и т. д.), то эти операции выносятся во внешние изоляты (Isolates) или выполняются асинхронно, чтобы не блокировать поток UI. BLoC легко сочетается с таким подходом: в событии можно запускать изолят, а по его завершении эмитить состояние с результатом. В литературе описаны подходы комбинирования BLoC с отдельными изолятами для повышения отзывчивости приложений [5], и они демонстрируют, что даже ресурсоемкие задачи можно выполнять без фризов интерфейса, сохраняя архитектурную чистоту кода.
Подводя итог оценке производительности: паттерн BLoC не оказывает негативного влияния на быстродействие Flutter-приложения. Напротив, при грамотном применении он позволяет уменьшить количество перестраиваемых виджетов и объем хранимых в памяти дублирующих данных, что положительно сказывается на потреблении ресурсов [4]. В худшем случае (для совсем простых экранов) накладные расходы BLoC минимальны и измеряются микросекундами или несколькими килобайтами памяти, что некритично даже для слабых устройств.
Заключение
В ходе проведенного анализа установлено, что применение паттерна BLoC в кроссплатформенных Flutter-приложениях позволяет добиться высокого качества архитектуры без ущерба для производительности. BLoC четко отделяет бизнес-логику от интерфейса, что упрощает масштабирование проекта, повышение повторного использования кода и проведение модульного тестирования. Измерения показали, что накладные расходы, связанные с обработкой событий и состояний, незначительны по сравнению с общей нагрузкой приложения; в сценариях с большим числом обновлений интерфейса использование BLoC даже снижает потребление ресурсов за счёт оптимизации перестроения UI [4].
Можно сделать вывод, что паттерн BLoC целесообразно применять в большинстве случаев разработки средних и крупных Flutter-приложений, где важна чистота архитектуры и долгосрочная поддерживаемость кода. В сравнении с более простыми подходами (такими как Provider) BLoC выигрывает в организованности кода и облегчает поддержку сложной бизнес-логики, хотя и требует больших первоначальных усилий при реализации. С точки зрения пользователя, приложение с архитектурой BLoC обеспечивает высокую отзывчивость и стабильность работы интерфейса.
Разумеется, выбор архитектурного решения должен учитывать конкретные требования проекта. Для очень простых приложений BLoC может оказаться избыточным, и в таких случаях оправдано применение минималистичных решений. Однако по мере роста приложения миграция на BLoC окупается: кодовая база остается устойчивой к усложнению функциональности, а производительность и пользовательский опыт остаются на высоком уровне. В итоге, паттерн BLoC зарекомендовал себя как эффективный и надежный подход в кроссплатформенной Flutter-разработке, объединяя преимущества четкой архитектуры и конкурентоспособной производительности.
Литература:
- Siddiqui A. Продвинутое управление состоянием в Flutter: Реализация шаблонов Provider, Bloc и Riverpod // Open Access, 2024. URL: https://open.zeba.academy/prodvinutoe-upravlenie-sostoyaniem-flutter-realizatsiya-shablonov-provider-bloc-riverpod/ (дата обращения: 10.05.2025).
- Flutter Documentation. Guide to app architecture — Flutter Developers Official Documentation, 2023. URL: https://docs.flutter.dev/app-architecture/guide (дата обращения: 10.05.2025).
- Bloc Library Documentation. Architecture Overview — Официальная документация BLoC, 2023. URL: https://bloclibrary.dev/#/architecture (дата обращения: 10.05.2025).
- Prayoga R. R. et al. Performance Analysis of BLoC and Provider State Management Library on Flutter // Jurnal Mantik, vol. 5, no. 3, 2021, pp. 1591–1597. DOI: 10.33102/mantik.v5i3.1539.
- DevMonarch. BLoC vs Provider: Flutter State Management — a detailed comparison // Medium, 11.01.2023. URL: https://medium.com/@DevMonarch/bloc-vs-provider-flutter-state-management-a-detailed-comparison-5a932e9033dd (дата обращения: 15.05.2025).

