
Привет, Хабр.
WebView-приложения — это боль. Тормоза, убогий UX, мгновенный реджект от Apple по пункту 4.2 («Minimum Functionality»). Обычно это просто браузер без адресной строки, за который стыдно брать деньги.
Я решил не делать очередную "обертку", а подойти к задаче инженерно. Моя цель: платформа, где WebView — лишь контентный слот, обернутый в полноценный нативный UI на Flutter.
В этой статье:
Архитектура: Как связать JS и Dart через двусторонний мост.
Server-Driven UI: Как менять нативные элементы управления (табы, шторки) без пересборки.
5 платформ из одной кодовой базы: iOS, Android, macOS, Windows, Linux.
Bypass Apple Review: Как мы проходим модерацию, отдавая нативные пермишены и экраны.
Никакой воды, только архитектура и код.
1. Почему Flutter, а не React Native или KMP?
Когда встала задача сделать «конвейер» по сборке приложений, главными требованиями были:
Производительность: Оболочка не должна лагать.
Мультиплатформенность: Заказчики хотят не только мобилку, но часто и десктопные версии (для корпоративных порталов, киосков и т.д.).
Почему Flutter победил:
Единая кодовая база для 5 платформ. React Native хорош для мобилок, но с десктопом там все сложнее (хотя подвижки есть). Flutter позволил мне написать один движок, который собирается в
.apk,.ipa,.exe,.dmgи Linux-бинарник.Контроль над пикселями. Flutter рисует интерфейс сам (через Skia/Impeller). Это значит, что мои нативные меню, шторки и диалоги выглядят идентично везде.
Platform Channels. Удобная связь с нативной частью для специфических функций (например, оплаты или сложные пуши).
2. Архитектура «Умного WebView»
Обычный веб-враппер — это виджет WebView на весь экран. Мой подход — это «слоеный пирог».
+---------------------------------------------------+
| LAYER 1: FLUTTER NATIVE SHELL |
| +-----------------------------------------------+ |
| | [=] App Bar Bottom Nav Bar [..] | |
| | Native Loaders Native Permissions | |
| +-----------------------------------------------+ |
+------------------------+--------------------------+
|
(Commands / Haptic / Push)
|
+------------------------v--------------------------+
| LAYER 2: JS BRIDGE |
| [ Dart Code ] <==========> [ JavaScript ] |
+------------------------+--------------------------+
|
(Events / Auth Tokens)
|
+------------------------v--------------------------+
| LAYER 3: WEBVIEW |
| +-----------------------------------------------+ |
| | <html> | |
| | Your Website (SPA/SSR) | |
| | React / Vue / Angular / WordPress | |
| | </html> | |
| +-----------------------------------------------+ |
+---------------------------------------------------+
Слой 1: Нативная оболочка (Flutter)
Пользователь не должен чувствовать, что он в браузере. Поэтому вся навигация вынесена из HTML в нативный код:
Bottom Navigation Bar — это нативный Flutter-виджет. Он переключает табы мгновенно, с правильной анимацией.
AppBar / Drawers — тоже нативные.
Loaders & Errors — если интернет пропал, мы показываем красивый нативный экран «Нет сети» с кнопкой «Повторить», а не стандартную ошибку браузера.
Слой 2: Мост (JS Bridge)
Чтобы сайт и приложение общались, я использую двусторонний канал связи.
Сценарий: Пользователь нажимает «Оформить заказ» на сайте. Как это работает:
JS на сайте отправляет событие:
AppLikeWeb.postMessage(JSON.stringify({event: 'purchase', value: 100}));Dart-код ловит это сообщение через
JavascriptChannel.-
Приложение парсит событие и запускает нативные действия:
Вибрация телефона (Haptic Feedback).
Отправка события в AppsFlyer (через нативный SDK, а не через JS-пиксель, что надежнее).
Запрос оценки приложения (In-App Review).
// Упрощенный пример обработки на стороне Flutter
JavascriptChannel(
name: 'AppLikeWeb',
onMessageReceived: (JavascriptMessage message) {
final data = jsonDecode(message.message);
switch (data['event']) {
case 'purchase':
AnalyticsService.trackPurchase(data['value']);
HapticFeedback.mediumImpact();
break;
// ... другие кейсы
}
},
)
3. Киллер-фича: Server-Driven UI (SDUI)
Самая большая боль мобильной разработки — цикл обновлений. Заказчик говорит: «Давайте покрасим нижнее меню в красный к распродаже» или «Уберем вкладку 'Профиль'». В классике: Правка кода -> Сборка -> Заливка в Store -> Ревью (1-3 дня) -> Раскатка.
Я внедрил Server-Driven UI. При старте приложение делает легкий запрос к API конфига.
Что можно менять "на лету" без пересборки:
Шапка (Header): Скрыть/показать, изменить заголовок (вместо title сайта), выровнять текст (слева/центр).
Навигация: Добавить стрелки "Назад", "Меню" (kebab menu).
Цвета: Полный контроль над палитрой (фон, границы, иконки, текст).
Внешние интеграции: Включить AdMob, сменить ключи OneSignal/Firebase.
(Подробнее о том, как работает дизайн-конструктор, я писал в блоге компании)

Пример фрагмента конфига:
{
"theme": {
"primary_color": "#FF5733",
"app_bar_style": "floating"
},
"navigation": {
"type": "bottom_bar",
"items": [
{
"id": "home",
"icon": "home_outlined",
"url": "https://mysite.com/",
"label": "Главная"
},
{
"id": "cart",
"icon": "shopping_cart",
"url": "https://mysite.com/cart",
"badge_source_js": "getCartCount()"
}
]
},
"features": {
"pull_to_refresh": true,
"admob_enabled": false
}
}
Flutter приложение, получая этот JSON, «на лету» перестраивает виджеты. Если мне нужно отключить рекламу в версии для iOS, я просто меняю флаг на бэкенде — приложение обновляется при следующем перезапуске. Zero days на ревью.
4. Борьба с Apple Guideline 4.2: Permissions и Actions
Apple ненавидит приложения, которые просто открывают сайт. Их главный аргумент: «Где здесь приложение? Это должен быть Safari».
Мы решаем эту проблему, давая пользователю контроль над Permissions (разрешениями). В панели управления владелец приложения может тонко настроить доступ:
Location (Foreground/Background): Критично для сервисов доставки и карт.
Camera & Microphone: Для видеозвонков или сканирования QR.
Все изменения применяются мгновенно. Когда модератор видит, что приложение запрашивает специфические права и имеет нативные экраны управления ими — вопрос «почему это не сайт» отпадает.
(Мы выкатили отдельный апдейт Actions & Permissions, чтобы закрыть этот вопрос раз и навсегда)

Кроме того, мы реализовали глубокую интеграцию нативных фич:
Биометрия: FaceID/TouchID для входа.
Оффлайн-режим: Красивые заглушки вместо "белого экрана".
Нативная навигация: TabBar с сохранением стейта.
5. Монетизация и Вовлечение (Firebase + AdMob)
Бизнес делает приложения не ради галочки, а ради денег и удержания клиентов.
Push-уведомления (Firebase Cloud Messaging): Мы сделали интеграцию максимально простой. Вставляете конфиг Firebase -> включаете галочку -> пуши работают.
Поддержка Topics и сегментации аудитории.
Отправка маркетинговых акций или системных алертов.
(Кейс внедрения пушей описан здесь)

AdMob (Реклама): Можно включить показ рекламы (баннеры, межстраничные) на конкретных экранах. Это позволяет медиа-ресурсам зарабатывать на мобильном трафике, сохраняя нативный UX.

6. Инфраструктура: Как собирать сотни приложений?
Ручная сборка в Xcode/Android Studio для каждого клиента — это путь в ад. Процесс в AppLikeWeb полностью автоматизирован.
После того как клиент нажал "Create App", запускается CI/CD пайплайн. В конце процесса в разделе Actions появляются ссылки на скачивание всех артефактов:
Android: APK, AAB (для Google Play), исходный код.
iOS: IPA файл.
Desktop: DMG (macOS), ZIP (Windows), AppImage/DEB/RPM (Linux).
Это позволяет клиенту получить готовый бинарник под любую платформу за 10-15 минут без настройки окружения.

Пример того, как это выглядит "под капотом" (Bash + Fastlane):
# 1. Генерируем native assets (иконки и сплеши)
flutter pub run flutter_launcher_icons:main
flutter pub run flutter_native_splash:create
# 2. Подменяем Bundle ID / Package Name
dart run rename_app.dart --bundleId "com.client.$APP_ID" --appName "$APP_NAME"
# 3. Сборка и подпись (iOS)
flutter build ipa --release --export-options-plist=ExportOptions.plist
fastlane pilot upload --ipa build/ios/ipa/*.ipa
7. Технические грабли (Подводные камни)
Конечно, не все было гладко. Вот ТОП-3 проблемы, с которыми я столкнулся при скрещивании ежа (Flutter) и ужа (WebView):
Проблема №1: OAuth и "Disallowed User Agent"
Google запрещает авторизацию через WebView из соображений безопасности. Если пользователь нажмет "Войти через Google" на сайте внутри приложения, он увидит ошибку 403. Решение: Пришлось внедрять подмену User-Agent на лету, маскируясь под обычный мобильный браузер, либо (в идеале) перехватывать ссылку на OAuth и открывать её в SFSafariViewController / ChromeCustomTabs, где авторизация разрешена, а куки потом прокидывать обратно.
Проблема №2: Загрузка файлов на Android
На iOS <input type="file"> работает из коробки. На Android WebView по умолчанию игнорирует клики на загрузку файлов. Решение: Нужно переопределять onShowFileChooser в WebChromeClient и вручную вызывать нативный пикер файлов Flutter'а, а затем передавать результат обратно в WebView.
Проблема №3: Синхронизация Cookies
Если пользователь залогинился в нативной части (например, через нативный экран логина), WebView об этом не знает.
Решение: Использование CookieManager. При старте приложения я беру токен из SecureStorage и инжектю его в куки WebView до того, как загрузится первая страница.
Future<void> syncCookies(String url) async {
final cookieManager = WebViewCookieManager();
final token = await _storage.read(key: 'auth_token');
if (token != null) {
await cookieManager.setCookie(
WebViewCookie(
name: 'access_token',
value: token,
domain: Uri.parse(url).host,
path: '/',
),
);
}
}
// Вызываем это строго перед controller.loadRequest(uri)
Заключение
Проект AppLikeWeb — это попытка сделать инструмент, который экономит месяцы разработки для контентных проектов, E-commerce и медиа. Это не замена нативной разработке для сложного банкинга или игр, но для 90% бизнесов, у которых уже есть хороший мобильный сайт — это самый быстрый способ попасть в стор с качественным UX.
Сейчас я активно допиливаю интеграции с аналитикой и улучшаю поддержку десктопных ОС.
Буду рад вашему фидбеку: какие нативные фичи вы считаете критически важными для гибридных приложений? И был ли у вас опыт (успешный или нет) прохождения ревью Apple с подобными решениями?
Статья написана при поддержке моих друзей:
MegaV.app — быстрый и безопасный VPN и не только.
PinVPS — надежные облачные серверы (NVMe, Ryzen) для хостинга ваших проектов и бэкенда.
(Ссылка на сам сервис из статьи: https://applikeweb.com/)