Что такое Flutter?
Flutter – это платформа с открытым исходным кодом, который разработан и поддерживается Google. Фронтенд-разработчики и фуллстек-разработчики используют Flutter для создания пользовательского интерфейса приложений (UI) для различных платформ с применением единой базы кода.
После выпуска Flutter в 2018 году эта платформа в основном поддерживала разработку мобильных приложений. Теперь Flutter поддерживает разработку приложений на шести платформах: iOS, Android, веб-интерфейс, Windows, MacOS и Linux.
Как Flutter упрощает разработку приложений?
Flutter упрощает процесс создания единообразных привлекательных пользовательских интерфейсов для приложения на шести поддерживаемых платформах.
Поскольку Flutter – это платформа разработки, поддерживающая несколько платформ, мы сначала сравним разработку на ней с разработкой для конкретных платформ. Затем мы рассмотрим функции, уникальные для Flutter.
Сравнение разработки для конкретной платформы с разработкой кроссплатформенных приложений
Разработка приложения для отдельной платформы, например iOS, называется разработкой платформозависимых приложений. Разработка кроссплатформенных приложений, напротив, является созданием приложений для нескольких платформ с единой базой кода.
Разработка платформозависимых приложений
Поскольку при разработке платформозависимых приложений разработчики создают программное обеспечение для конкретной платформы, они имеют полный доступ ко встроенным функциям устройства. Этим обычно обусловлена более высокая производительность и скорость платформозависимых приложений по сравнению с кроссплатформенными.
С другой стороны, если вы хотите, чтобы приложение работало на нескольких платформах, то для разработки платформозависимого приложения потребуется больше кода и разработчиков. Помимо этого, платформозависимые приложения сложнее запускать на различных платформах с единообразным пользовательским интерфейсом. В этом случае пригодится платформа для разработки кроссплатформенных приложений, такая как Flutter.
Разработка кроссплатформенных приложений
Разработка кроссплатформенных приложений дает разработчикам возможность использовать один язык программирования и одну базу кода, чтобы создать приложение для нескольких платформ. Если вы выпускаете приложение для нескольких платформ, кроссплатформенная разработка потребует меньших затрат и меньшего времени, чем разработка платформозависимого приложения.
Также этот процесс позволяет разработчиков создавать более единообразные интерфейсы для различных платформ.
Этот подход может иметь недостатки по сравнению с разработкой платформозависимых приложений, в том числе ограниченный доступ к функциям устройства. Однако во Flutter реализованы возможности, которые делают разработку кроссплатформенных приложений более простой и высокоэффективной.
Преимущества Flutter
Flutter выделяется среди платформ для кроссплатформенной разработки благодаря следующим преимуществам.
- Производительность близка к производительности платформозависимых приложений. Flutter использует язык программирования Dart и компилируется в машинный код. Устройства понимают этот код, что обеспечивает быструю работу и высокую производительность.
- Быстрое, единообразное и настраиваемое отображение. Flutter не полагается на платформозависимые инструменты отображения, а использует для отображения пользовательского интерфейса графическую библиотеку Google Skia с открытым исходным кодом. Это предоставляет пользователям единообразные визуальные элементы, независимо от платформы, используемой для доступа к приложению.
- Удобные инструменты для разработчиков Компания Google создала Flutter с акцентом на простоте использования. Благодаря таким функциям, как горячая перезагрузка, разработчики могут предварительно просматривать, как будут выглядеть изменения в коде, без потери состояния. Другие инструменты, такие как инспектор виджетов, упрощают визуализацию и решение проблем в макетах пользовательского интерфейса.
Какой язык программирования используется во Flutter?
Flutter использует язык программирования с открытым исходным кодом Dart, который также разработан в Google. Dart оптимизирован для создания пользовательского интерфейса, и многие его преимущества используются во Flutter.
Например, одна из возможностей Dart, которая используется во Flutter, – защита от нулевых ссылок. Защита от нулевых указателей упрощает обнаружение распространенных ошибок, называемых ошибками нулевых ссылок. Эта возможность сокращает время, затрачиваемое разработчиками на обслуживание кода, освобождая время на создание приложений.
Что представляют собой виджеты Flutter?
Разработчики создают макеты пользовательского интерфейса на Flutter с помощью виджетов. Это значит, что все, что пользователь видит на экране, от окон и панелей до кнопок и текста, состоит из виджетов.
Виджеты Flutter разработаны так, чтобы разработчикам было просто настраивать их. Во Flutter это реализовано с использованием композиционного подхода. Это значит, что большинство виджетов состоит из меньших виджетов и большинство базовых виджетов имеют специфическое предназначение. Это позволяет разработчикам комбинировать или редактировать виджеты для создания новых виджетов.
Flutter отображает виджеты с помощью собственного графического движка, не полагаясь на встроенные виджеты платформы. Благодаря этому пользователям предоставляется один и тот же внешний вид приложения Flutter на разных платформах. Кроме того, данный подход предоставляет разработчикам гибкость, потому что некоторые виджеты Flutter могут выполнять функции, недоступные для виджетов, специфических для платформы.
Кроме того, Flutter упрощает использование виджетов, разработанных сообществом. Архитектура Flutter поддерживает множество библиотек виджетов, и Flutter поддерживает сообщество в разработке и поддержке новых.
Типы виджетов Flutter
Flutter поставляется с обширным каталогом виджетов при загрузке. В каталоге 14 категорий, в том числе стили, Купертино (виджеты в стиле iOS) и Материальные виджеты (виджеты, соответствующие рекомендациям Google по материальному дизайну).
Кроме того, Flutter поставляется с макетами и темами, благодаря чему разработчики могут сразу приступать к компоновке.
Какова поддержка Flutter?
Flutter поддерживается компанией Google и активным сообществом разработчиков программного обеспечения с открытым исходным кодом в Reddit, Discord, Slack, Stack Overflow и Gitter. Компания Google постоянно обновляла Flutter с момента выпуска в 2018 году, включая обновление Flutter 3 в 2022 году, которое распространило стабильную поддержку на macOS и Linux.
Чтобы было проще осваивать Flutter, компания Google снабдила эту платформу обширной документацией и учебными пособиями, приведенными на сайте Flutter. Для привлечения пользователей Flutter компания Google проводит глобальные мероприятия, поддерживает проекты сообщества и спонсирует состязания разработчиков. О предстоящих событиях можно узнать на сайте Flutter.
Сообщество Flutter создало тысячи сторонних пакетов и отличных инструментов, которые упрощают разработку. Эти библиотеки доступны на pub.dev.
Как AWS поддерживает Flutter?
Flutter помогает вам создавать те части приложения, с которыми взаимодействует пользователь. Но для разработки приложений требуется реализовать множество функций, которые не видны пользователям, например аутентификацию, хранение файлов и аналитику. Здесь пригодятся AWS Amplify и Amplify Flutter.
AWS Amplify представляет собой платформу для создания защищенных и масштабируемых приложений как для мобильных устройств, так и для сети Интернет. Благодаря поддержке iOS, Android, интернет-интерфейса, React Native и Flutter, AWS Amplify ускоряет и упрощает разработку приложений в AWS.
Amplify Flutter – это набор инструментов и библиотек, который дает возможность предоставлять, компоновать и развертывать серверные части приложений Flutter. Вы можете использовать Amplify Flutter, чтобы подключать приложения Flutter к AWS и решать общие проблемы серверной части.
Используйте Amplify Flutter как решение для серверной части
Amplify Flutter дает возможность работать с AWS и добавлять общие функции серверной части в приложение Flutter.
- Аналитика Amplify Flutter позволяет собирать данные отслеживания для пользователей в Amazon Pinpoint. Вы можете с легкостью записывать события и настраивать метрики и атрибуты в соответствии со своими потребностями.
- API В Amplify Flutter реализованы надежные возможности API. API GraphQL помогает вам получать данные в серверной части при поддержке AWS AppSync. API и обработчики REST используют API шлюз Amazon и AWS Lambda, помогая вам отправлять запросы к серверной части.
- Аутентификация Amplify Flutter позволяет вам аутентифицировать пользователей и реализовать формы регистрации и входа, а также многофакторную аутентификацию. За кулисами этот сервис проводит необходимую авторизацию в других категориях Amplify. Amplify Flutter поддерживает пул пользователей и пул удостоверений Cognito с самого начала использования.
- Хранилище данных Amplify Flutter дает вам возможность использовать распределенные общие данные без написания дополнительного кода для сценариев с подключением и без него. Благодаря этому работа с распределенными данными разных пользователей становится такой же простой, как работа с локальными данными. Amplify DataStore автоматически создает версии данных и использует AppSync для обнаружения и устранения конфликтов в облаке.
- Хранилище Amplify Flutter дает возможность передавать, загружать и удалять объекты в хранилище. Кроме того, Amplify Flutter поставляется со встроенной поддержкой Простого сервиса хранения данных Amazon (Amazon S3).
Flutter – единственная правильная кроссплатформа для приложений
Команда Surf — пионеры разработки Flutter-приложений в России. Мы активно участвуем в развитии сообщества: основали первый русскоязычный подкаст о технологии, регулярно выпускаем статьи, выступаем на конференциях и делимся своими библиотеками на GitHub с разработчиками по всему миру.
Используя этот фреймворк, мы успешно запустили внутрикорпоративное мобильное решение для сотрудников KFC, мобильный банк Росбанк и приложение для крупнейшей сети аптек Ригла.
В статье раскрываем основные преимущества Flutter-разработки для бизнеса и рассматриваем кейсы известных компаний по всему миру.

Почему стоит разработать приложение на Dart и Flutter
Язык программирования Dart
Flutter использует язык программирования Dart с собственным менеджером пакетов, несколькими компиляторами, транспилерами и синтаксическим анализатором. Компилятор Dart переводит код, написанный разработчиками, в понятный для машины — и любые ошибки, которые могут возникнуть в процессе интерпретации, предотвращаются.
Язык программирования Dart подходит для JIT (Just-In-Time) и AOT (Ahead-Of-Time) компиляции. Во время разработки специалисты используют JIT-компиляцию, а, когда продукт готов к выпуску — AOT-компиляцию. Благодаря ей, в релизе программа получает высокую производительность.
С Dart процессы разработки, выполнения и запуска значительно ускоряются и это далеко не все преимущества данного языка программирования:
- разработчики переиспользуют до 70% кода для мобильного приложения в web и наоборот,
- для него разработана виртуальная машина Dart VM, использующая язык программирования Dart как промежуточный и выступающая в роли интерпретатора,
- Dart можно компилировать и интерпретировать в другие языки программирования. Пример такой компиляции — преобразование кода в JavaScript для использования с Node.js
- разработчик получает в свое распоряжение Hot Reload — фичу, с которой можно подгрузить код в приложение в режиме отладки максимально быстро,
- просматривая каждое изменение без необходимости пересборки продукта.
Dart несложен в освоении. Опытные разработчики пишут на нём через пару недель обучения, а новички — через 3-4 недели.
Нативный UI
При работе нативных мобильных приложений на Android для отрисовки визуальных элементов используется код Java. Системные библиотеки платформы передают объекту Canvas компоненты, отвечающие за отрисовку, и платформа рендерит Canvas с помощью «холста» Skia. Для продуктов на iOS используются другие системные библиотеки и алгоритм отрисовки — это усложняет и замедляет процесс реализации визуальных компонентов.
Flutter стремится убрать различия между отрисовкой на разных платформах. Для этого используется уровень абстракций, созданный поверх нативных UI библиотек обеих платформ.
Чтобы свести к минимуму число абстракций, вместо системных библиотек UI виджетов Flutter предлагает собственный набор виджетов, а визуальная часть отрисовывается при помощи движка Skia, который скрыт от глаз и находится на уровне ядра фреймворка. В результате мы значительно сокращаем время разработки, получая при этом качественный интерфейс с плавной анимацией. «Мимикрируем» под нативные решения так, что пользователь не видит разницы.

Flutter-приложения, разработанные командой Surf

Нативные приложения, разработанные командой Surf
6 основных платформ на одной кодовой базе
Одно из главных преимуществ Flutter-разработки на Dart — возможность создавать продукты для шести популярных платформ, используя единую кодовую базу. В зависимости от специфики проекта, от 80 до 95% кодовой базы можно переиспользовать. Адаптации нужны, однако сделать их намного проще, чем писать с нуля отдельное решение для каждой платформы.

Быстрая разработка и time to market
Разработав более 15 решений для банков, финтех-проектов, ритейла и других бизнесов, мы сравнили ресурсозатраты при использовании Flutter с языком программирования Dart и нативных технологий. Выяснили, что с этим кроссплатформенным фреймворком бизнес экономит:
- до 45,6% времени и стоимости разработки,
- 70,5% на тестировании — в том числе на автотестах,
- 33,3% на дизайне,
- 70% на стоимости поддержки,
- 20% времени, затрачиваемого на добавление функций после запуска MVP.
Выбирая для создания приложений Dart и Flutter, мы привлекаем меньше специалистов, чем при нативной разработке, поскольку используем единую кодовую базу для разных платформ. Не нужно собирать команду для разработки продукта под каждую платформу — отсюда большая разница в стоимости его создания, поддержки и развития.
Как пример, вот так выглядит типичная Flutter-команда и команда нативной разработки:

Состав Flutter-команды и команды для разработки нативного мобильного продукта
Флаттер экономит ресурсы без потери качества
Чтобы создать Flutter, инженеры Google проанализировали позитивный и негативный опыт всех существующих кроссплатформенных решений и вывели идеальный «рецепт». Так, флаттер стал технологией, с которой можно разрабатывать продукты, не уступающие в качестве нативным — при этом значительно экономить ресурсы.
- одновременно запустить три канала продаж — мобильную, десктоп и веб-версию продукта — на единой кодовой базе,
- реализовать любой дизайн интерфейса в сжатые сроки, используя мощный графический движок и набор кастомизируемых виджетов,
- добиться плавной анимации, переходов между экранами — с этим фреймворком доступна производительность от 60 до 120 кадров в секунду в зависимости от дисплея.
Узнайте всё о преимуществах Flutter и оцените наши кейсы
Примеры успешных приложений написанных на Dart и Flutter
BMW
Чтобы выпускать новые продуктовые решения и функционал одновременно для владельцев разных автомобилей в разных регионах, BMW решила выпустить отдельную платформу. Сравнив все существующие кроссплатформенные технологии, компания выбрала Flutter, который даёт возможность безопасно проводить эксперименты и обеспечивает непрерывное развертывание. В результате команда создала дружественный интерфейс для конечных пользователей.

Alibaba
Компания выбрала Dart и Flutter для мобильного решения Xianyu, крупнейшего секонд-хенд маркетплейса. Этот пример наглядно показывает преимущества флаттера: плавная анимация, высокая частота обновления дисплея, возможность значительно сократить время разработки для двух платформ, а также быстро добавлять новые фичи стали решающими при выборе фронтэнд стека.

Росбанк
В рамках этого проекта мы разработали первое в России и второе в мире мобильное банковское приложение на Flutter; помогли клиенту наладить развитие проекта ин-хаус, быстро собрав для него команду. Вместе мы предоставили пользователям мощный инструмент для решения любых задач, связанных с банкингом. С Dart и Flutter удалось значительно сократить сроки разработки продукта для двух платформ.

KFC
Клиент обратился к нам с задачей — разработать мобильное решение для сотрудников, внедрив электронные чек-листы, в которых они могли бы отмечать статусы по выполненной работе. Flutter помог вдвое сократить бюджет проекта по сравнению с нативными технологиями, которые изначально планировал использовать клиент. Так, мы смогли ускорить процесс разработки и оперативно предоставить сотрудникам электронный инструмент, экономящий их время.

Ещё больше примеров проектов на Flutter — в портфолио Surf
Какие вопросы нам часто задают о Flutter
За счет чего флаттер разработка быстрее нативной?
Флаттер позволяет создавать продукты для шести основных платформ, при этом основная часть кодовой базы проекта переиспользуется.
Вот пример: если у нас есть веб-приложение с возможностью просматривать список товаров и добавлять их в корзину, мы можем использовать уже написанные виджеты отображения и на мобильных устройствах и на смарт-часах.
Говорят, что продукты на Флаттер много весят. Какой размер у мобильных продуктов под разные платформы?
Продукты, разработанные с технологией флаттер, немного тяжелее нативных, поскольку внутри содержатся вспомогательные зависимости для работы приложения. Однако с механизмом Tree shaking ненужные компоненты не включаются в сборку. Например, устаревшие статические изображения, которые никогда не используются, или код, который никогда не вызовется, не попадут неё.
Для наглядности, один из наших небольших проектов под Android весит 8,5 Мб, а проект со сложной графикой и Swift-кодом под iOS — около 30 Мб.
Может ли Флаттер работать с [название библиотеки под популярную мобильную платформу]?
Да, но эти библиотеки будут доступны только под ту платформу, под которую были написаны изначально.
Как происходит шифрование?
Для шифрования/дешифрования на флаттер есть вменяемый порт Bouncy Castle. Он написан на языке программирования Dart.
Как Флаттер справляется со сложными анимациями?
Для реализации сложных графических эффектов разработчики используют модуль отрисовки CanvasKit. Так, мы получаем высокое качество отображения визуальных компонентов, плавность анимации, переходов. А еще разработчики флаттер недавно добавили новый графический движок Impeller. Благодаря ему стала возможной разработка интерфейса ProMotion дисплеев с частотой обновления 120 Гц.
Можно ли на Флаттере писать продукты, которые работают офлайн?
Да. Флаттер без ограничений поддерживает работу и с базами данных, и с файловой системой: можно реализовать любой вариант кэширования, оптимальный для вашего бизнеса.
Перспективы развития фреймворка
Команда Google, создавшая Flutter, активно использует его и язык программирования Dart в собственных проектах — и это долгосрочная гарантия для компаний, выбравших этот фреймворк. Поскольку он получил широкое распространение среди разработчиков и предпринимателей по всему миру, у Google есть веские основания регулярно обновлять SDK, а вам не придется менять Flutter на другой фреймворк из-за отсутствия поддержки и обновлений.
По данным Statista, Flutter — самая популярная кроссплатформенная технология. Флаттеристы всего мира совместно развивают фреймворк, и мы не исключение: эксперты Surf создали собственные инструменты и библиотеки для оптимизации процесса разработки, используют их в проектах и делятся с другими Flutter-командами.
Как работает Flutter

Что такое Widgets, Elements, BuildContext, RenderOject, Bindings.
Вступление
В прошлом году (прим: в 2018), когда я начал свое путешествие в сказочный мир Flutter, в Интернете было очень мало информации по сравнению с тем, что есть сегодня. Сейчас, несмотря на то, что уже написано много материалов, лишь небольшая их часть рассказывает о том, как на самом деле работает Flutter.
Что же такое Widgets (виджеты), Elements (элементы), BuildContext? Почему Flutter быстрый? Почему иногда он работает не так, как ожидается? Что такое деревья и зачем они нужны?
В 95% случаев при написании приложения вы будете иметь дело только с виджетами, чтобы что-то отображать на экране или взаимодействовать с ним. Но неужели вы никогда не задумывались, как вся эта магия работает внутри? Как система узнает, когда обновить экран и какие части должны быть обновлены?
Содержание:
- Вступление
- Часть 1: Предыстория
- Немного об устройстве
- Интерфейс между кодом и устройством
- Управление Flutter Framework рендерингом Flutter Engine
- RenderView и RenderObject
- Инициализация bindings
- SchedulerBinding
- GestureBinding
- RendererBinding
- WidgetsBinding
- Неизменяемая конфигурация
- Иерархическая структура виджетов
- Лес за деревьями
- Понимание Element в дереве
- 3 категории виджетов
- Типы элементов
- Как виджеты и элементы работают вместе
- onDrawFrame()
- Насколько полезным может быть BuildContext?
- Забавы ради
Часть 1: Предыстория
В первой части статьи представлены некоторые ключевые понятия, которые будут использованы во второй части материала и помогут лучше понять Flutter.
Немного об устройстве
Давайте начнем с конца и вернемся к основам.
Когда вы смотрите на свое устройство или, точнее, на приложение, запущенное на вашем устройстве, вы видите только экран.
На самом деле, всё, что вы видите – это пиксели, которые вместе составляют 2-мерное изображение, и когда вы касаетесь экрана пальцем, устройство распознает только положение вашего пальца на стекле.
Вся магия приложения (с визуальной точки зрения) в большинстве случаев заключается в обновлении этого изображения на основе следующих взаимодействий:
- с экраном устройства (например, палец на стекле)
- с сетью (например, связь с сервером)
- со временем (например, анимация)
- с другими внешними датчиками
Визуализация изображения на экране обеспечивается аппаратным обеспечением (дисплеем), которое регулярно (обычно 60 раз в секунду) обновляет дисплей. Эта называется «частотой обновления» и выражается в Гц (Герцах).
Дисплей получает информацию для отображения от GPU (Graphics Processing Unit), представляющего собой специализированную электронную схему, оптимизированную и предназначенную для быстрого формирования изображения из некоторых данных (полигонов и текстур). Количество раз в секунду, которое графический процессор может генерировать «изображение» (=буфер кадров) для отображения и отправки его на аппаратное обеспечение, называется кадровой частотой (прим: frame rate). Это измеряется с помощью блока кадров в секунду (например, 60 кадров в секунду или 60fps).
Вы, возможно, спросите меня, почему я начал эту статью с понятий 2-мерного изображения, отображаемого GPU / аппаратным обеспечением и датчиком физического стекла и какова связь с обычными виджетами Flutter?
Думаю, что будет легче понять, как на самом деле работает Flutter, если мы посмотрим на него с этой точки зрения, так как одна из главных целей приложения Flutter – создать это 2-мерное изображение и дать возможность взаимодействовать с ним. Также потому, что во Flutter, хотите верьте, хотите нет, почти все обусловлено необходимостью обновления экрана быстро и в нужный момент!
Интерфейс между кодом и устройством
Так или иначе, все интересующиеся Flutter уже видели следующую картинку, которая описывает архитектуру высокого уровня Flutter.

Когда мы пишем приложение Flutter, используя Dart, мы остаемся на уровне Flutter Framework (выделено зеленым цветом).
Flutter Framework взаимодействует с Flutter Engine (синим цветом) через слой абстракции, называемый Window. Этот уровень абстракции предоставляет ряд API для косвенного взаимодействия с устройством.
Также через этот уровень абстракции Flutter Engine уведомляет Flutter Framework, когда:
- событие, представляющее интерес, происходит на уровне устройства (изменение ориентации, изменение настроек, проблема с памятью, состояние работы приложения…)
- какое-то событие происходит на уровне стекла (=жест)
- канал платформы отправляет некоторые данные
- но также и в основном, когда Flutter Engine готов к рендерингу нового кадра
Управление Flutter Framework рендерингом Flutter Engine
В это сложно поверить, но это правда. За исключением некоторых случаев (cм. ниже) ни один код Flutter Framework не выполняется без запуска рендеринга Flutter Engine.
- Gesture / Жест (= событие на стекле)
- Сообщения платформы (= сообщения, которые создаются устройством, например, GPS)
- Сообщения устройства (= сообщения, которые относятся к изменению состояния устройства, например, ориентация, приложение, отправленное в фоновом режиме, предупреждения памяти, настройки устройства…)
- Future или http-ответы
(Между нами говоря, на самом деле можно применить визуальное изменение без вызова от Flutter Engine, но это не рекомендуется делать)
Вы меня спросите: «Если какой-то код, связанный с жестом, выполняется и вызывает визуальное изменение или если я использую timer для задания периодичности задачи, которая приводит к визуальным изменениям (например, анимация), то как это работает?»
Если вы хотите, чтобы произошло визуальное изменение или чтобы какой-то код выполнялся на основе таймера, то вам нужно сообщить Flutter Engine, что что-то должно быть отрисовано.
Обычно при следующем обновлении Flutter Engine обращается к Flutter Framework для выполнения некоторого кода и в конечном итоге предоставляет новую сцену для рендеринга.
Поэтому важный вопрос заключается в том, как движок Flutter организует все поведение приложения на основе рендеринга.
Чтобы вам получить представление о внутренних механизмах, посмотрите на следующую анимацию:
Краткое объяснение (более подробная информация будет позже):
- Некоторые внешние события (жест, http-ответы и тд) или даже futures могут запускать задачи, которые приводят к необходимости обновления отображения. Соответствующее сообщение отправляется Flutter Engine (= Schedule Frame)
- Когда Flutter Engine готов приступить к обновлению рендеринга, он создает Begin Frame запрос
- Этот Begin Frame запрос перехватывается Flutter Framework, который выполняет задачи, связанные в основном с Tickers (например, анимацию)
- Эти задачи могут повторно создать запрос для более поздней отрисовки (пример: анимация не закончила своё выполнение, и для завершения ей потребуется получить еще один Begin Frame на более позднем этапе)
- Далее Flutter Engine отправляет Draw Frame, который перехватывается Flutter Framework, который будет искать любые задачи, связанные с обновлением макета с точки зрения структуры и размера
- После того, как все эти задачи выполнены, он переходит к задачам, связанным с обновлением макета с точки зрения отрисовки
- Если на экране есть что-то, что нужно нарисовать, то новая сцена (Scene) для визуализации отправляется в Flutter Engine, который обновит экран
- Затем Flutter Framework выполняет все задачи, которые будут выполняться после завершения рендеринга (= PostFrame callbacks), и любые другие последующие задачи, не связанные с рендерингом
- … и этот процесс начинается снова и снова
RenderView и RenderObject
Прежде чем погружаться в детали, связанные с потоком действий, самое время ввести понятие Rendering Tree.
Как уже говорилось ранее, всё в конечном итоге преобразуется в пиксели, которые будут отображаться на экране, и Flutter Framework преобразует Widgets, которые мы используем для разработки приложения, в визуальные блоки, которые будут отображаться на экране.
Данные визуальные части соответствуют объектам, называемым RenderObject, которые используются для:
- определения некоторой области экрана с точки зрения размеров, положения, геометрии, а также с точки зрения «rendered content»
- определения зон экрана, на которые могут повлиять жесты (= касания пальцев)
Набор всех RenderObject формирует дерево, называемое Render Tree. В верхней части этого дерева (= root) мы находим RenderView.
RenderView представляет общую поверхность для объектов Render Tree и является специальной версией RenderObject.

Визуально мы могли бы представить все это следующим образом:
Cвязь между Widget и RenderObject будет рассмотрена далее. А пока пришло время немного углубиться…
Инициализация bindings
При запуске Flutter приложения сначала вызывается функция main() , который в конечном итоге вызовет метод runApp(Widget app) .
Во время вызова метода runApp() Flutter Framework инициализирует интерфейсы между собой и Flutter Engine. Эти интерфейсы называются bindings (прим: привязки).
Введение в привязки
Привязки предназначены для того, чтобы быть связующим звеном между фреймворком и движком Flutter. Только с помощью привязок можно обмениваться данными между Flutter Framework и Flutter Engine.
(Есть только одно исключение из этого правила – RenderView, но мы обсудим это позже).Каждая привязка отвечает за обработку набора конкретных задач, действий, событий, сгруппированных по области деятельности.
На момент написания этой статьи во Flutter Framework насчитывается 8 привязок.
Ниже приведены 4 из них, которые будут рассмотрены в этой статье:
Для полноты картины упомяну и остальные 4:
- ServicesBinding: отвечает за обработку сообщений, отправленных каналом платформы (platform channel)
- PaintingBinding: отвечает за обработку кэша изображений
- SemanticsBinding: зарезервировано для последующей реализации всего, что связано с семантикой
- TestWidgetsFlutterBinding: используется библиотекой тестов виджетов
Можно также упомянуть WidgetsFlutterBinding, но на самом деле это не является привязкой, а скорее своего рода «инициализатором привязки».
На следующей диаграмме показано взаимодействие между привязками, которые я собираюсь рассмотреть далее, и Flutter Engine.

Давайте посмотрим на каждую из этих «основных» привязок.
SchedulerBinding
У этой привязки есть две основные обязанности:
- Сказать Flutter Engine: «Эй! В следующий раз, когда вы не будете заняты, «разбудите» меня, чтобы я мог немного поработать и сказать вам, что отрендерить, или, если мне нужно, чтобы вы вызвали меня позже. «
- Слушать и реагировать на такие «тревожные пробуждения» (см. ниже)
Когда SchedulerBinding запрашивает «тревожное пробуждение»?
- Когда Ticker должен отработать новый tick
Например, у вас есть анимация, вы ее запускаете. Анимация кадрируется с помощью Ticker, который с регулярным интервалом (= tick) вызывается для выполнения обратного вызова. Чтобы запустить такой обратный вызов, нам нужно сказать Flutter Engine, чтобы он «разбудил» нас при следующем обновлении (= Begin Frame). Это запустит обратный вызов ticker для выполнения его задачи. Если ticker все еще нужно продолжить выполнение, то в конце своей задачи он вызовет SchedulerBinding для планирования другого кадра. - Когда надо обновить отображение
Например, надо отработать событие, которое приводит к визуальному изменению (пример: обновление цвета части экрана, прокрутка, добавление / удаление чего-либо с экрана), для этого нам нужно предпринять необходимые шаги, чтобы в конечном итоге показать на экране обновленное изображение. В этом случае, когда происходит такое изменение, Flutter Framework вызывает SchedulerBinding для планирования другого кадра с помощью Flutter Engine. (Позже мы увидим, как это работает на самом деле)
GestureBinding
Данная привязка слушает взаимодействие с движком в терминах «пальца» (= жест).
В частности, он отвечает за прием данных, относящихся к пальцу, и за определение того, с какой частью (частями) экрана работают жесты. Затем он соответственно уведомляет об этом / этих частях.
RendererBinding
Эта привязка является связующим звеном между Flutter Engine и Render Tree. Она отвечает за:
- прослушивание событий, создаваемых движком, чтобы сообщить об изменениях, применяемых пользователем через настройки устройства, которые влияют на визуальные эффекты и / или семантику
- сообщение движку об изменениях, которые будут применены к отображению
Чтобы предоставить изменения, которые будут отображаться на экране, RendererBinding отвечает за управление PipelineOwner и инициализацию RenderView.
PipelineOwner — это своего рода оркестратор, который знает, что нужно сделать с RenderObject в соответствии с компоновой, и координирует эти действия.
WidgetsBinding
Данная привязка прослушивает изменения, применяемые пользователем через настройки устройства, которые влияют на язык (= locale) и семантику.
Я предполагаю, что на более позднем этапе развития Flutter все события, связанные с семантикой, будут перенесены в SemanticsBinding, но на момент написания этой статьи это еще не так.
Кроме этого, WidgetsBinding является связующим звеном между виджетами и Flutter Engine. Она отвечает за:
- управление процессом обработки изменений структуры виджетов
- вызов рендеринга
Обработка изменений структуры виджетов осуществляется с помощью BuildOwner.
BuildOwner отслеживает, какие виджеты нуждаются в перестройке, и обрабатывает другие задачи, которые применяются к структуре виджетов в целом.
Часть 2. От виджетов к пикселям
Теперь, когда мы познакомились с основами внутренней работы Flutter, пришло время поговорить о виджетах.
Во всей документации Flutter вы прочитаете, что всё Widgets (виджеты).
Это почти правильно. Но для того, чтобы быть немного более точным, я бы скорее сказал:
Со стороны разработчика, всё, что связано с пользовательским интерфейсом с точки зрения компоновки и взаимодействия, делается с помощью виджетов.
К чему такая точность? К тому, что Widget позволяет разработчику определить часть экрана с точки зрения размеров, содержания, компоновки и взаимодействия, НО за этим есть гораздо большее. Так что же такое Widget на самом деле?
Неизменяемая конфигурация
Если вы посмотрите исходный код Flutter, то заметите следующее определение класса Widget.
@immutable abstract class Widget extends DiagnosticableTree < const Widget(< this.key >); final Key key; . >Аннотация «@immutable» очень важна и говорит нам, что любая переменная в классе Widget должна быть FINAL, другими словами: «определена и назначена ОДИН РАЗ ДЛЯ ВСЕХ«. Таким образом, после создания экземпляр Widget больше не сможет изменить свои внутренние переменные.
Так как Widget неизменяемый, то можно его считать статичной конфигурацией
Иерархическая структура виджетов
Когда вы разрабатываете с помощью Flutter, вы определяете структуру своего экрана(ов), используя виджеты примерно так:
Widget build(BuildContext context)
В этом примере используется 7 виджетов, которые вместе образуют иерархическую структуру. Очень упрощенная схема, основанная на данном коде, выглядит следующим образом:

Как можно заметить, представленная схема выглядит, как дерево, где SafeArea является его корнем.
Лес за деревьями
Как вы уже знаете, виджет сам по себе может быть агрегацией других виджетов. В качестве примера можно изменить предыдущий код следующим образом:
Widget build(BuildContext context)
Данный вариант предполагает, что виджет «MyOwnWidget» сам будет отображать SafeArea, Scaffold. Но самое главное в этом примере заключается в том, что
Widget может представлять лист, узел в дереве, даже само дерево или, почему бы и нет, лес деревьев.
Понимание Element в дереве
При чём здесь это?
Как будет показано далее, чтобы иметь возможность генерировать пиксели, которые составляют изображение, отображаемое на устройстве, Flutter должен знать в деталях все маленькие части, которые составляют экран, и, чтобы определить все части, ему необходимо знать раскрытие всех виджетов.
Чтобы проиллюстрировать данный момент, рассмотрим принцип матрёшки: в закрытом состоянии вы видите только 1 куклу, но она содержит другую, которая в свою очередь содержит ещё одну и так далее.

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

Формулировка «дерево виджетов» (Widget tree) существует только для облегчения понимания, поскольку программисты используют виджеты, но во Flutter НЕТ дерева виджетов!
На самом деле, правильнее будет сказать «дерево элементов» (tree of Elements)
Настало время ввести понятие элемента (Element).
Каждому виджету соответствует один элемент. Элементы связаны друг с другом и образуют дерево. Следовательно элемент является ссылкой на что-то в дереве.
Для начала подумайте об элементе как о узле, который имеет родителя и, возможно, ребенка. Связывая их вместе через отношение родитель — ребёнок, мы получаем древовидную структуру.

Как вы можете видеть, элемент указывает на один виджет, а также может указывать на RenderObject.
Даже лучше… Element указывает на Widget, который создал этот Element!
Давайте подведём итоги:
- Нет никакого дерева виджетов, но есть дерево элементов
- Элементы создаются виджетами
- Элемент ссылается на виджет, который его создал
- Элементы связаны вместе с родительскими отношениями
- У элемента может быть «ребёнок»
- Элементы также могут указывать на RenderObject
Элементы определяют, как части отображаемых блоков связаны друг с другом
Для того, чтобы лучше представить, где понятие элемент подходит, давайте рассмотрим следующее визуальное представление:

Как вы можете заметить, дерево элементов является фактической связью между виджетами и RenderObjects.
Но почему Widget создает Element?
3 категории виджетов
Во Flutter виджеты разделены на 3 категории, лично я называю их следующим образом (но это только мой способ классифицировать их):
- Proxy
Основная задача этих виджетов состоит в том, чтобы хранить некоторую информацию (которая должна быть доступной для виджетов), части древовидной структуры, основанной на Proxy. Примером таких виджетов является InheritedWidget или LayoutId.
Эти виджеты не принимают непосредственного участия в формировании пользовательского интерфейса, но используются для получения информации, которую они могут предоставить. - Renderer
Данные виджеты имеют непосредственное отношение к компоновке экрана, поскольку они определяют (или используются для определения) размеры, положение, отрисовку. Типичными примерами являются: Row, Column, Stack, а также Padding, Align, Opacity, RawImage. - Component
Это другие виджеты, которые предоставляют непосредственно не окончательную информацию, связанную с размерами, позициями, внешним видом, а скорее данные (или подсказки), которые будут использоваться для получения той самой финальной информации. Эти виджеты обычно называются компонентами.
Примеры: RaisedButton, Scaffold, Text, GestureDetector, Container.

В этом PDF-файле перечислена большая часть виджетов, сгруппированных по категориям.
Почему это разделение важно? Потому что в зависимости от категории виджета, соответствующий тип элемента связан с…
Типы элементов
Есть несколько типов элементов:
Как вы можете видеть на картинке выше, элементы делятся на 2 основных типа:
- ComponentElement
Эти элементы напрямую не отвечают за отрисовку какой-либо части отображения. - RenderObjectElement
Данные элементы отвечают за части отображаемого изображения на экране.
Отлично! Столько информации, но как всё это связано друг с другом и почему об этом интересно рассказать?
Как виджеты и элементы работают вместе
Во Flutter вся механика основана на инвалидации элемента или renderObject.
Инвалидация элемента может быть сделана следующими способами:
- используя setState , который инвалидирует весь StatefulElement (обратите внимание, что я намеренно не говорю StatefulWidget)
- через уведомления, обрабатываемые proxyElement (например, InheritedWidget), который инвалидирует любой элемент, зависящий от данного proxyElement
Результатом инвалидации является то, что на соответствующий элемент появляется ссылка в списке dirty элементов.
Инвалидация renderObject означает, что структура элементов никак не меняется, но происходит изменение на уровне renderObject, например:
- изменение его размеров, положения, геометрии.
- необходимо что-то перекрасить, например, когда вы просто меняете цвет фона, стиль шрифт.
Результатом такой инвалидации является ссылка на соответствующий renderObject в списке объектов рендеринга (renderObjects), которые необходимо перестроить или перекрасить.
Независимо от типа инвалидации вызывается SchedulerBinding (помните такое?) для запроса к Flutter Engine, чтобы тот запланировал новый кадр.
Это именно тот момент, когда Flutter Engine «будит» SchedulerBinding и происходит вся магия.
onDrawFrame()
Ранее в этой статье мы отметили, что у SchedulerBinding две основные обязанности, одна из которых заключается в готовности обрабатывать запросы, создаваемые Flutter Engine, связанные с перестроением кадра. Это идеальный момент, чтобы сосредоточиться на этом.
Ниже на частичной диаграмме последовательности показано, что происходит, когда SchedulerBinding получает запрос onDrawFrame() от Flutter Engine.

Шаг 1. Элементы
Вызывается WidgetsBinding, и данная привязка сначала рассматривает изменения, связанные с элементами. WidgetsBinding вызывает метод buildScope объекта buildOwner, так как BuildOwner отвечает за обработку дерева элементов. Этот метод проходит по списку dirty элементов и запрашивает их перестроение (rebuild).
Основными принципами данного метода-перестроения ( rebuild() ) являются:
- Следует запрос на перестроение элемента (это займёт большую часть времени), вызывая метод build() виджета, на который ссылается этот элемент (= метод Widget build (BuildContext context) ). Данный метод build() вернёт новый виджет
- Если у элемента нет «детей», то для нового виджета создаётся элемент (см. ниже) (прим: inflateWidget), в противном случае
- новый виджет сравнивается с тем, на который ссылается дочерний элемент элемента
- Если они взаимозаменяемы (= тот же тип виджета и ключ), то обновление происходит и дочерний элемент сохраняется.
- Если они не взаимозаменяемы, то дочерний элемент отбрасывается (~ discarded) и для нового виджета создаётся элемент
- Данный новый элемент монтируется как дочерний элемент элемента. (монтируется (mounted) = вставляется в дерево элементов)
Следующая анимация попытается сделать это объяснение немного нагляднее.
Примечание по виджетам и элементам
Для нового виджета создаётся элемент конкретного типа, соответствущего категории виджета, а именно:
- InheritedWidget ->InheritedElement
- StatefulWidget ->StatefulElement
- StatelessWidget ->StatelessElement
- InheritedModel ->InheritedModelElement
- InheritedNotifier ->InheritedNotifierElement
- LeafRenderObjectWidget ->LeafRenderObjectElement
- SingleChildRenderObjectWidget ->SingleChildRenderObjectElement
- MultiChildRenderObjectWidget ->MultiChildRenderObjectElement
- ParentDataWidget ->ParentDataElement
У каждого из этих типов элементов есть свое собственное поведение. Например:
- StatefulElement вызовет метод widget.createState() при инициализации, который создаст состояние (State) и свяжет его с элементом
- Когда элемент типа RenderObjectElement смонтирован, то он создаёт RenderObject. Этот объект renderObject будет добавлен в Render Tree и связан с элементом.
Шаг 2. renderObjects
Теперь после завершения всех действий, связанных с dirty элементами, Element Tree является стабильным. Поэтому пришло время рассмотреть процесс визуализации.
Поскольку RendererBinding отвечает за обработку Render Tree, WidgetsBinding вызывает метод drawFrame RendererBinding.
Ниже на частичной диаграмме показана последовательность действий, выполняемых во время запроса drawFrame().

На этом шаге выполняются следующие действия:
- Каждый renderObject, помеченный как dirty, запрашивается для выполнения его компоновки (то есть вычисления его размеров и геометрии)
- Каждый renderObject, помеченный как «нуждающийся в перерисовке», перерисовывается, используя свой метод layer
- Результирующая сцена формируется и отправляется во Flutter Engine, чтобы последний передал ее на экран устройства
- Наконец, также обновляется семантика и отправляется во Flutter Engine
В конце этого потока действий экран устройства обновляется.
Часть 3: Обработка жестов
Жесты (= события, связанные с действиями пальца на стекле) обрабатываются с помощью GestureBinding.
Когда Flutter Engine отправляет информацию о событии, связанном с жестом, через window.onPointerDataPacket API, то GestureBinding перехватывает её, выполняет некоторую буферизацию и:
- преобразует координаты, выдаваемые Flutter Engine, в соответствие с device pixel ratio, а затем
- запрашивает у renderView список всех RenderObjects, которые находятся в части экрана, относящейся к координатам события
- затем проходит по полученному списку renderObjects и отправляет связанное событие каждому из них
- если renderObject «слушает» события такого типа, то он его обрабатывает
Надеюсь, сейчас понятно, насколько важны renderObjects.
Часть 4: Анимации
Эта часть статьи посвящена понятию анимации и глубокому пониманию Ticker.
Когда вы работаете с анимациями, то вы обычно используете AnimationController или любой виджет для анимаций (прим: AnimatedCrossFade).
Во Flutter всё, что связано с анимациями, относится к Ticker. У Ticker, когда он активен, есть только одна задача: «он просит SchedulerBinding зарегистрировать обратный вызов и сообщить Flutter Engine, что надо разбудить его, когда появится новый обратный вызов». Когда Flutter Engine готов, он вызывает SchedulerBinding через запрос: «onBeginFrame«. SchedulerBinding обращается к списку обратных вызовов ticker и выполняет каждый из них.
Каждый tick перехватывается «заинтересованным» контроллером для его обработки. Если анимация завершена, то ticker «отключён», иначе ticker запрашивает SchedulerBinding для планирования нового обратного вызова. И так далее.
Полная картина
Теперь мы узнали, как работает Flutter:

BuildContext
Напоследок вернёмся к диаграмме, которая показывает различные типы элементов, и рассмотрим сигнатуру корневого Element:
abstract class Element extends DiagnosticableTree implements BuildContext
Мы видим тот самый всем известный BuildContext! Но что это такое?
BuildContext — это интерфейс, определяющий ряд геттеров и методов, которые могут быть реализованы элементом. В основном BuildContext используется в методе build() StatelessWidget или State для StatefulWidget.
- обновляемому виджету (внутри методов build или builder )
- StatefulWidget, связанному со State, в котором вы ссылаетесь на переменную контекста.
Это означает, что большинство разработчиков постоянно работают с элементами, даже не зная об этом.
Насколько полезным может быть BuildContext?
Поскольку BuildContext соответствует элементу, связанному с виджетом, а также местоположению виджета в дереве, то BuildContext может быть полезен, когда надо:
- получить ссылку на объект RenderObject, соответствующий виджету (или, если виджет не является Renderer, то виджету-потомку)
- получить размер RenderObject
- обратиться к дереву. Это используется фактически всеми виджетами, которые обычно реализуют метод of (например, MediaQuery.of(context) , Theme.of(context) …)
Забавы ради
Теперь, когда мы осознали, что BuildContext – это элемент, я хотел бы показать вам другой способ его использования. Ниже совершенно бесполезный код позволяет StatelessWidget обновить себя так, как если бы это был StatefulWidget, но без использования setState() , а с помощью BuildContext.
ПРЕДУПРЕЖДЕНИЕ
Пожалуйста, не используйте этот код!Его единственная задача – продемонстрировать, что StatelessWidget может запрашивать обновление.
Если вам нужно некоторое состояние с виджетом, пожалуйста, используйте StatefulWidget.void main() < runApp(MaterialApp(home: TestPage(),)); >class TestPage extends StatelessWidget < // final because a Widget is immutable (remember?) final bag = ; @override Widget build(BuildContext context) < return Scaffold( appBar: AppBar(title: Text('Stateless ??')), body: Container( child: Center( child: GestureDetector( child: Container( width: 50.0, height: 50.0, color: bag["first"] ? Colors.red : Colors.blue, ), onTap: ()< bag["first"] = !bag["first"]; // // This is the trick // (context as Element).markNeedsBuild(); >), ), ), ); > >Если честно, когда вы вызываете setState() , то в конечном итоге он делает то же самое: _element.markNeedsBuild() .
Заключение
Вы скажете: «Ещё одна длинная статья». Но я подумал, что вам было бы интересно узнать, как построена архитектура Flutter, и решил напомнить, что всё было разработано, чтобы быть эффективным, масштабируемым и открытым для будущих расширений. Кроме того, ключевые понятия, такие как Widget, Element, BuildContext, RenderObject, не всегда очевидны для восприятия. Могу только надеяться, что эта статья была для вас полезной.
Ждите новых новостей уже скоро. А пока позвольте пожелать вам успешного программирования.
PS Всю критику, вопросы и предложения по переводу буду рад услышать в (личных) сообщениях.
PSS Ещё раз ссылка на оригинальную статью Flutter internals от Didier Boelens, так как одной в шапке перевода для такого большого материала мало)- Программирование
- Разработка мобильных приложений
- Dart
- Flutter
Наш гайд по архитектуре Flutter
Flutter — популярный бесплатный фреймворк для разработки мобильного интерфейса, созданный компанией Google. Он позволяет разрабатывать кроссплатформенные приложения, а значит, переиспользовать единый код в приложениях под Android и iOS. Благодаря этому разработчики могут создавать приложения высокого качества, с которыми комфортно работать пользователям различных платформ.
В этой статье мы рассмотрим архитектуру мобильных приложений во Flutter и принципы, лежащие в основе этого фреймворка.
Основы архитектуры
Flutter состоит из двух основных частей — фреймворка с UI библиотекой и SDK.
- Фреймворк — это UI библиотека виджетов, состоящая из переиспользуемых элементов пользовательского интерфейса, к примеру, слайдеров, кнопок и полей ввода данных. Эти элементы можно персонализировать под потребности заказчика.
- SDK — это набор средств разработки, с его помощью можно компилировать код в нативный машинный для Android и iOS.
Во Flutter используется язык программирования Dart, созданный компанией Google в октябре 2011 года. Движок Flutter в основном написан на C++. Движок отвечает за реализацию базового API (поддержку специальных возможностей, среду выполнения Dart-кода, формат графического текста и модульной архитектуры) инструментами нижнего уровня.
С точки зрения операционной системы устройства Flutter-приложения комплектуются аналогично нативным. Слой embedder компилируется с ОС устройства и получает доступ к вводу данных и рендерингу. Кроме того, с помощью embedder код во Flutter можно интегрировать в виде модуля в уже существующее нативное приложение.
Flutter представляет собой многослойную систему с возможностью расширения. По сути, это последовательность независимых библиотек, каждая из которых связана со слоем под ней. Каждую часть на уровне фреймворка можно заменить.
Разработчики работают с Flutter через фреймворк, написанный на языке Dart. В него входят инструменты для вёрстки, платформы и библиотеки. Снизу вверх слои расположены в следующем порядке:
- Основные сервисы и классы: анимации, жесты и отрисовка.
- Слой рендеринга: средства для вёрстки и построения каталога объектов рендеринга.
- Слой виджетов реализует модель реактивного программирования. Каждому классу в слое виджетов соответствует отрисовываемый объект в слое рендеринга. Помимо этого, слой виджетов позволяет задавать комбинации переиспользуемых классов.
- Библиотеки Cupertino и Material предоставляют набор элементов управления, который реализует дизайн-систему iOS или Material.

Компоненты архитектуры Flutter
Основной элемент во Flutter — виджет. Виджеты составляют основу компонентов архитектуры. С их помощью реализуются распознавание жестов и сложная анимация. Затем объект рендерится на графическом движке Skia, написанном на C/C++, а потом поступает команда ЦП и графическому процессору отрисовать объект на устройстве. Вот классический пример модульной архитектуры Flutter — простой stateful виджет.
class MyApp extends StatefulWidget < @override StatecreateState() < return MyAppState() >> class MyAppState extends State < @override Widget build (BuildContext context) < return OneOrMoreWidgets; >>Состав виджета
Виджет состоит из блоков узкоспециализированного назначения, которые в совокупности могут произвести более сложный результат. Концепция виджетов используется и на уровне слоя виджетов: для отображения анимации и иллюстраций, форматирования, навигации, управления состояниями, применения тем и взаимодействия с пользователем.
На уровне слоёв анимации и рендеринга тоже есть виджеты. Кроме того, во Flutter есть вспомогательные виджеты, к которым также применим подход объединения нескольких сущностей в одну.
Иерархия виджетов
Относительно друг друга виджеты находятся в композиционной иерархии. Каждый виджет находится внутри «родителя» и получает от него контекст. Такая иерархия сохраняется вплоть до корневого виджета. Кроме того, иерархия виджетов является расширяемой, чтобы можно было увеличить число возможных комбинаций.
В ответ на событие приложение обновляет свой UI, передавая фреймворку команду заменить виджет в иерархии на подходящий. Фреймворк, сравнив виджет и его замену, оперативно обновляет пользовательский интерфейс.
Классы и категории виджетов
Виджеты делятся на два основных класса: stateful и stateless. Если уникальные характеристики виджета меняются под влиянием различных факторов, он относится к классу stateful. Если у виджета нет состояния mutable и его свойства не меняются, он считается stateless.
Также виджеты во Flutter можно разбить на категории по типам доступных им фич:
- Виджеты форматирования
С их помощью можно собрать несколько виджетов в один. К наиболее популярным относятся Center, Column, Container, Row и Stack.
- Независимые от платформы/ базовые виджеты
С их помощью можно создавать любые пользовательские интерфейсы без привязки к платформе. Сюда входят text, image и icon.
- Платформоспецифичные виджеты
Виджеты, специфичные для Android или iOS. Android-специфичные виджеты называются Material виджетами, так как они используют принципы Material Design от Android OS. iOS-специфичные виджеты называются виджетами Cupertino. Они формируются в соответствии с принципами Human Interface от Apple.
- Виджеты поддержания состояний
Все виджеты во Flutter делятся на stateful и stateless.

Рендеринг во Flutter
Наверняка, вы задавались вопросом: как может кроссплатформенный фреймворк Flutter обеспечивать производительность, сопоставимую с нативом?
Давайте вспомним, как работают нативные приложения для Android. Для отрисовки используется Java код. Системные библиотеки Android передают компоненты, отвечающие за отрисовку, объекту Canvas. Его Android рендерит с помощью «холста» Skia.
Кроссплатформенные фреймворки стремятся убрать различия в представлении каждой платформы, используя уровень абстракций, созданный поверх нативных UI библиотек iOS и Android.
Код в приложении обычно написан на интерпретируемом языке вроде JavaScript, которому необходимо взаимодействовать с iOS системой при помощи Objective C или с Android на языке Java, чтобы отобразить UI. В результате приложение потребляет много ресурсов, особенно если логика приложения и UI часто взаимодействуют.
Flutter вместо системных библиотек виджетов UI использует собственный набор виджетов, таким образом сводя к минимуму число абстракций. Визуальная часть отрисовывается при помощи языка программирования Dart. Он компилируется в нативный код, который затем используется Skia для рендеринга.

Взаимодействие с кодом на других языках
Flutter предлагает разработчику большой выбор механизмов для интеграции. Он позволяет добавлять кастомный код для мобильных и десктопных приложений через канал платформы. Это простой механизм взаимодействия в исходном приложении кода, специфичного для платформы, и вашего кода на Dart.
Через канал можно обмениваться сообщениями между платформенным компонентом, написанным на языке типа Swift или Kotlin, и частью, написанной на Dart. Передаваемые данные сериализуются с Dart в стандартный формат, а затем десериализуются в эквивалентные данные на Kotlin или Swift.

Преимущества архитектуры Flutter
- Простота. Flutter позволяет создавать приложения проще и быстрей.
- Быстрая компиляция кода. С помощью Flutter можно вносить изменения в код и фиксить баги практически в реальном времени.
- Простое и быстрое создание MVP. Если вам нужно как можно скорее сделать мобильное приложение, Flutter — отличный выбор. Кроме того, разработка на нём в разы дешевле, так как вам не нужно писать разный код для двух мобильных приложений — iOS и Android.
- Удобная MVVP архитектура. Архитектура MVVP (Model-View-View-Model) становится альтернативой архитектурным паттернам Android. Она обеспечивает прозрачность интерфейс и упрощает тестирование, так как препятствует слиянию логики приложения и UI.
- Преимущества Open Source архитектуры. На сегодня для Flutter доступно 34 репозиториев на Github. Тысячи разработчиков по всему миру пользуются репозиторием Flutter на Github, поддерживают и дополняют его.
Особенности архитектуры Flutter в проектах Surf
У Surf самая большая в России команда разработчиков на Flutter. Мы реализовали на этом фреймворке успешные проекты для банков, фудтеха, e-commerce-приложения.
На старте работы с Flutter мы не могли решить, какой подход к архитектуре взять за основу: BLoC, Redux, Vanilla или MobX. В итоге, решили не отдавать предпочтение ни одному из них, но выстроить систему, которая позволит адаптировать фреймворк под специфические задачи нашей компании. Мы решили создать архитектуру, которая позволит нашим Android и Flutter разработчикам быстро обмениваться задачами и передавать их друг другу.
Для этого мы адаптировали паттерн MWWM (Model-View-View-Model) под Flutter архитектуру. Впоследствии мы заменили слово View на Widget, чтобы не было путаницы. Основное преимущество такого подхода в том, что он позволяет отделить вёрстку от логики, причём как от бизнес-логики, так и от логики слоя представления.
Конечно, в проектах мы используем и обычные виджеты Flutter. Сейчас наш стек технологий для проектирования архитектуры — это комбинация нескольких шаблонов, собранных в единый пакет, как показано на схеме ниже. В пакет входит инжектор, связующее звено между слоями и surf_mwwm. Этот подход мы использовали для всех своих проектов на Flutter.

Приложения на Flutter от Surf
Ригла — крупнейшая российская сеть аптек, которая представлена на рынке под тремя брендами: «Ригла», «Будь здоров» и «Живика». У каждого из них своя целевая аудитория и ценовая политика.
Клиент хотел разработать отдельные приложения под Android и iOS для каждого бренда — итого, 6 приложений. Команда Surf написала единую кодовую базу на Flutter и на её основе реализовала приложения для всех трёх брендов. Это позволило заказчику сэкономить 40% бюджета разработки. Проект мы выполнили за пять месяцев, а первый релиз для бренда «Ригла» вышел всего через три месяца.

Surf разработал приложение Росбанка — это первый мобильный B2B банк на Flutter в Европе и второй в мире. Приложение помогает бизнес-клиентам банка решать в несколько тапов разные финансовые задачи: оплачивать налоги, сохранять и отправлять реквизиты, отслеживать и оплачивать счета.

The Hole — это приложение для видеостриминга, которое Surf разработал для продюсерской компании Medium Quality. Заказчик — крупнейший паблишер российского YouTube, выпускающий множество разнообразных шоу с суммарной аудиторией 16,2 миллионов пользователей и 920 миллионами просмотров. Приложение транслирует контент, который удобно смотреть на мобильном устройстве (многие шоу сняты вертикально, а горизонтальные видео обрезаются автоматически).
Разработка на Flutter помогла клиенту сэкономить около 40% бюджета по сравнению с нативной разработкой. При этом фреймворк смог обеспечить плавность работы UI и анимаций перехода между экранами, что особенно важно для видеостримингового сервиса.
Мы разработали для KFC Digital Successful Routine (DSR) — ERP-систему, которая автоматизировала финансовую аналитику и управление бизнес-процессами. Вся информация теперь оцифрована и хранится на сервере, а значит, компания может вести статистику, строить отчеты и отслеживать показатели в реальном времени. Она стала удобным инструментом для менеджеров самых разных уровней. Например, менеджеры смен могут заполнять чек-листы, контролировать приход и уход сотрудников и ставить им задачи онлайн. Такая автоматизация рутины экономит им 10 часов в неделю.
Один из основных модулей DSR — это система дашбордов и отчетов по операционным показателям работы ресторана, которые необходимы менеджменту для анализа текущей ситуации и принятия оперативных решений. Платформа агрегирует все чеки из ресторанов KFC в режиме реального времени. На их основе строятся графики и отчеты для руководителей.

Подведём итоги
- Архитектура Flutter представляет собой расширяемую многослойную систему и и использует независимые библиотеки.
- Приложение на Flutter состоит из виджетов.
- Flutter обеспечивает производительность приложения, сопоставимую с нативными.
- Преимущества Flutter: это достаточно простой в использовании фреймворк, у него есть активное сообщество на Github. Кроме того, архитектура Flutter позволяет быстро собрать MVP.
У Surf самая большая в России команда разработчиков на Flutter. Мы реализовали на этом фреймворке успешные проекты для банков, фудтеха, e-commerce-приложения. Если вы хотите разработать приложение на Flutter, напишите нам и мы свяжемся с вами в течение суток.