Кросплатформна розробка мобільного застосунку на Flutter
Flutter 3.x на основі Dart компілює код безпосередньо в нативний ARM bytecode через AOT-компіляцію — ні JavaScript-мосту, ні WebView. Це принципова відмінність від React Native, де будь-який вклик нативного API проходить через асинхронний міст. На практиці різниця відчутна там, де важлива анімація, користувацький рендеринг та робота з жестами: Flutter рисує кожний кадр через власний механізм Skia (або Impeller з Flutter 3.10+), не покладаючись на нативні віджети платформи.
Якщо проект вимагає 60/120fps на Flutter 3.16+ з Impeller на iOS, окремої розмови з командою не уникнути: Impeller за замовчуванням увімкнений на iOS, на Android — прапором --enable-impeller. Там, де старий Skia справлявся, Impeller може поводитися інакше при першій компіляції шейдерів.
Де Flutter перемагає, а де потрібна обережність
Кросплатформний підхід виправданий, коли UI-логіка однакова на iOS та Android, а продукт потрібен швидко. Flutter закриває 80–90% потреб через стандартні віджети Material та Cupertino. Проблеми починаються на краях.
Залежні від платформи API. Bluetooth Low Energy — через flutter_blue_plus, але його поведінка на Android 12+ (коли Google змінив дозволи з ACCESS_FINE_LOCATION на BLUETOOTH_SCAN) вимагає окремої обробки. На iOS — окремий ключ NSBluetoothAlwaysUsageDescription у Info.plist плюс фоновий режим bluetooth-central. Пропустите — App Store відхилить за guideline 5.1.1.
Фонове виконання. Dart Isolates не вирішують проблему фонових завдань на рівні ОС. Для фонової геолокації — background_locator_2 + WorkManager на Android (через пакет workmanager). На iOS — CLLocationManager у режимі allowsBackgroundLocationUpdates. Тут Flutter дає лише тонку обгортку, реальна логіка — нативна.
Канали платформи. Коли готового плагіна немає, пишемо MethodChannel або EventChannel. Типовий випадок — інтеграція з POS-терміналом через SDK виробника (Ingenico, PAX): SDK доступний тільки як .aar для Android та .framework для iOS, обгорнемо в нативний код, пробросимо через канал. Тут «кросплатформність» закінчується рівно на межі каналу.
Як будуємо архітектуру
Для проектів від 5 екранів використовуємо чисту архітектуру з розділенням на шари data, domain, presentation. Керування станом — Riverpod 2.x (провайдери типу AsyncNotifierProvider замість застарілого ChangeNotifierProvider). BLoC застосовуємо там, де команда вже з ним працювала або потрібен строгий event-driven потік з тестованістю кожного стану.
Навігація — go_router 13.x з підтримкою deep links та веб-маршрутизацією (якщо Flutter Web потрібен в проекті). Старий Navigator 1.0 уникаємо — при складних вкладених маршрутах стек ламається непередбачувано.
DI — через get_it + injectable. Генерація коду через build_runner скорочує boilerplate, але вимагає дисципліни: freezed для immutable моделей, json_serializable для серіалізації, drift або isar для локальної БД. isar в 3.x показує помітно швидше hive на операціях із колекціями — в одному проекті з 200k записів різниця становила ~3x на фільтруванні.
Приклад із практики. Застосунок агрегатора таксі — три користувацькі ролі (пасажир, водій, диспетчер), real-time оновлення через WebSocket (web_socket_channel), карти через flutter_map + OpenStreetMap плитки (клієнт не хотів платити за Google Maps SDK). Фонові оновлення геопозиції водія — через WorkManager на Android та BGAppRefreshTask на iOS з обгорткою в Dart. Збирання реліза — Fastlane + GitHub Actions: один workflow збирає --release APK та IPA паралельно, підписує, завантажує в Firebase App Distribution для QA.
Тестування та CI
Unit-тести Dart — стандартний flutter_test. Integration-тести — пакет integration_test (запуск на реальному пристрої або емуляторі через flutter drive). Widget-тести охоплюють ключові компоненти: testWidgets з WidgetTester та pump/pumpAndSettle.
У CI (GitHub Actions / GitLab CI) розділяємо: flutter analyze + dart format --set-exit-if-changed на кожен PR, тести — паралельно на Android API 33 emulator та iOS Simulator. Збирання для TestFlight та Google Play Internal Track — через Fastlane Match для сертифікатів.
Продуктивність профілюємо в flutter run --profile + DevTools: Timeline view показує jank-фрейми (>16ms), Memory view — heap між rebuild-ами. Типові проблеми: надмірні setState у глибокому дереві, неоптимізовані зображення (декодуємо без cacheWidth/cacheHeight), дорогі обчислення в build() замість вивчених у compute().
Що впливає на терміни
| Масштаб проекту | Приблизні терміни |
|---|---|
| MVP, 5–10 екранів, REST API | 6–10 тижнів |
| Середній продукт, 15–30 екранів, offline-режим | 3–6 місяців |
| Складний продукт: real-time, платежі, карти, BLE | 6–12 місяців |
Вартість розраховується індивідуально після аналізу вимог — технічного завдання, макетів, описання інтеграцій.
Типові помилки при самостійній розробці
Найчастіше бачимо три проблеми при аудиті Flutter-проектів від інших команд.
Неправильна архітектура провайдерів. ChangeNotifier з глобальним станом, який перебудовує весь екран на будь-яку зміну. Перехід на Riverpod з гранульованими select-ами знімає проблему, але вимагає рефакторингу.
Канал платформи без обробки помилок. Нативний код кидає виняток → канал падає → застосунок крешується без зрозумілого повідомлення. Усі виклики через MethodChannel обгортаємо в try/catch PlatformException.
Ігнорування isolates для важких операцій. JSON десеріалізація 10MB відповіді на main isolate дає помітний фриз. compute() або ручна Isolate.spawn — обов'язково для даних >1MB.







