РозробкаБудь-який рівень

Flutter

Flutter — це відповідь Google на розробку під кілька платформ з однієї кодової бази, і підхід тут протилежний до React Native: замість просити віджети в операційної системи, Flutter постачає власний рушій рендерингу й малює кожен піксель сам. Це одне рішення пояснює однаковість, продуктивність, розмір бінарника й ті місця, де підхід буксує. На цій сторінці — конвеєр рендерингу, Dart, керування станом і те, куди Flutter пасує.

Рендеринг
Власний рушій, власні пікселі
Мова
Dart, AOT-компіляція
API платформи
Через канал

Flutter малює пікселі сам

Коли ви пишете кнопку у Flutter, не створюється ані UIButton, ані Android-Button. Flutter будує власне дерево, сам робить розкладку й віддає результат рушієві, який растеризує його на полотні на GPU. Платформа дає поверхню для малювання й майже нічого більше. Це і є той єдиний факт, який пояснює про фреймворк майже все інше.

Flutter не просить в операційної системи кнопку. Він будує власне дерево, сам робить розкладку й віддає рушієві полотно, щоб намалювати його на GPU, — кожен піксель на екрані малює Flutter. Тому той самий код виглядає однаково на обох платформах, а набір віджетів не змінюється, коли ОС оновлює власний. Ціна в тому, що все, що може дати лише ОС — камера, біометрія, сповіщення, дозволи, — доступне через канал платформи, а доступність і введення тексту доводиться мостити, а не успадковувати.

Конвеєр проходить через три дерева, і розуміння, що є що, робить поведінку фреймворку прозорою. Віджети — це ваш опис: незмінний, дешевий, який викидають і будують заново постійно. Елементи — це те, що справді змонтовано, і вони переживають перебудови; саме тому віджет можна перестворювати шістдесят разів на секунду, не перестворюючи його стан. Об’єкти рендерингу роблять розкладку й малювання, і саме вони дорогі, — тому Flutter так старається їх перевикористовувати.

Те, чого Flutter не малює, він мусить попросити. Камера, біометрія, сповіщення, Bluetooth, вибір файлів, покупки в застосунку — усе це доступне через канал платформи, тобто асинхронне повідомлення коду на Swift чи Kotlin. На практиці для поширених випадків пакет уже існує, але залежність цілком реальна: кожен такий пакет — це нативний код, який хтось має підтримувати проти двох платформ, що змінюються. Доступність і введення тексту працюють, але вони примощені до сервісів платформи, а не успадковані від нативного віджета, — тому іноді потребують уваги, якої нативний застосунок не потребував би.

Dart і чому це не мінус

Вивчати мову заради фреймворку звучить як витрата, і Dart — найменша версія цієї витрати, яку можна побажати: будь-хто, кому зручно з TypeScript, Java чи C#, читає його першого ж вечора. Він статично типізований, надійний щодо null-безпеки — значення, що може бути null, не можна використати як ненульове, і компілятор це забезпечує, — і однопотоковий у межах ізоляту, з async / await, які виглядатимуть знайомо.

Незвичайним Dart робить те, що він компілюється двома різними способами під дві різні цілі. У розробці він працює на віртуальній машині з JIT-компіляцією — саме це уможливлює гаряче перезавантаження зі збереженням стану: ви змінюєте віджет, і запущений застосунок оновлюється менш ніж за секунду, не втрачаючи, де ви були. Для релізу він компілюється заздалегідь у машинний код, тож у поставленому застосунку немає ані інтерпретатора, ані мосту в JavaScript. Швидкість розробки й продуктивність релізу зазвичай торгуються одна проти одної; тут — ні.

Паралельність працює як у Node: один потік на ізолят, із циклом подій, тож await ніколи не блокує інтерфейс, а довге обчислення — блокує. Для справді важкої роботи ви запускаєте ще один ізолят, який має власну пам’ять і спілкується повідомленнями, а не спільним станом. Церемонії тут більше, ніж із потоком, — і зникає весь клас гонок за даними, які потоки приносять.

Керування станом і чесна відповідь

У Flutter немає офіційного рішення для керування станом — тому кожна розмова про це перетворюється на порівняння пакетів. Корисно дивитися на поле за тим, скільки структури нав’язує кожен варіант, і правильна відповідь залежить значно більше від розміру команди, ніж від переваг бібліотеки.

  • setState

    Вбудований, локальний для одного віджета. Правильний для всього, чим володіє один екран, і хибний тієї миті, коли двом екранам потрібне те саме значення.

  • Provider / Riverpod

    Впровадження залежностей плюс спостережуваний стан. Riverpod сучасніший: безпечний на етапі компіляції, тестований і незалежний від дерева віджетів.

  • BLoC

    Події всередину, стани назовні, потоками. Забагато церемонії для малого застосунку; передбачувано й простежувано для великої команди на довгому проєкті.

Порада, що тримається незалежно від вибору: тримайте стан якомога ближче до місця, де його використовують, і виносьте назовні лише тоді, коли він справді потрібен другому місцю. Більшість складності, яку приписують керуванню станом у Flutter, створена власноруч: глобальне сховище, заведене першого ж дня для застосунку з чотирьох екранів, у якого спільного стану немає взагалі.

Як це виявляється в реальній поставці

Дві витрати варто знати до першого релізу. Рушій постачається всередині застосунку, тож бінарник Flutter стартує на кілька мегабайтів більшим за нативний — для більшості продуктів це байдуже, а на ринках, де розмір завантаження впливає на встановлення, це справжня проблема. І Flutter у вебі технічно підтримується, але рендерить у полотно, тож виділення тексту, SEO та доступність поводяться не як на звичайній сторінці: це пасує внутрішньому інструменту чи застосункоподібному продукту, а не публічному контентному сайту.

З іншого боку, інструментарій — це справжня сильна сторона. Гаряче перезавантаження змінює відчуття від роботи над інтерфейсом: ви ітеруєте відступи й кольори за секунди, а не за цикли збірки. Flutter DevTools показує дерево віджетів, кількість перебудов на кожен віджет і часову шкалу кадрів, що розділяє роботу UI й растеризації, — тож питання «чому тут смикається» має відповідь, а не здогад. Тести віджетів працюють без пристрою й достатньо швидкі, щоб тримати їх у pre-commit-хуку.

Де це вироджується

  • Метод build на сотні рядків перебудовує цілий екран через одне змінене значення.
  • Віджети, винесені в методи, а не в класи: const ніколи не зможе їх пропустити.
  • Пакет глобального стану, узятий першого дня для застосунку, у якого спільного стану немає.
  • Важкі обчислення в UI-ізоляті гублять кадри рівно так само, як заблокований цикл подій.
  • Один дизайн на обидві платформи без огляду на конвенції жодної з них.
  • Пакети з нативним кодом, узяті без перевірки, хто підтримує їх проти нових версій ОС.
  • Flutter web, обраний для публічного контентного сайту, де полотно — хибний вихід.

Коли це застосовувати

Застосовуйте, коли

  • Продукти, що мають виглядати й поводитися однаково на обох платформах — з однієї специфікації дизайну.
  • Власні брендовані інтерфейси, де ви й так збиралися перевизначати віджети платформи.
  • Насичений анімаціями інтерфейс: володіння конвеєром рендерингу тут саме й дає перевагу.
  • Команди без наявних навичок React, для яких Dart не більша перешкода, ніж будь-який інший новий стек.

Уникайте, коли

  • Застосунки, які мають відчуватися точно як платформа, — там успадковані нативні віджети і є суттю.
  • Продукти, де розмір завантаження вирішує кількість встановлень: рушій їде разом із бінарником.
  • Публічні контентні сайти: рендер у полотно — хибний інструмент для тексту й SEO.
  • Усе навколо найновіших API платформи: до Flutter вони доходять після того, як дійшли до нативу.

Було корисно?

Поділіться з тим, хто працює над тією ж задачею.