Webview в Flutter

В моем пет-проекте понадобилось мне выводить некоторые «виджеты» (партнерские веб-виджеты со своим маркером, а не виджеты Флаттера). Попытался разобраться с этим функционалом в Flutter. Здесь, конечно же, пришлось воспользоваться библиотекой с pub.dev. В этой статье я изложу итоги своих изысканий, чтобы в следующий раз не ходить по одним и тем же граблям. Может еще кому-то это поможет быстрее погрузиться в тему.

Рассмотрел три самых популярных. Использовал в проекте самую популярную webview_flutter от flutter.dev. По началу удивлялся, за что так много лайков, но разобравшись немного, понял, что на данный момент это и правда лучшее решение.

Webview библиотеки на pub.dev

Настройки

Классически, чтобы использовать библиотеку, нужно прописать ее в pubspec.yaml. В моем случае подключены webview_flutter: ^2.1.1 и url_launcher: ^6.0.12. launcher вам может и не пригодится, а мне нужен, объясню позже, зачем.

По умолчанию, при создании проекта у меня прописывается минимальная версия SDK Android 16, но плагин работает только начиная с 19. Поэтому нужно в манифесте поменять. (Если использовать Virtuals Display, то нужно ставить 20+ минимальную версию).

Ошибка, если не поменять минимальную версию

Веб-представление поддерживает в Андроиде два режима: виртуальные дисплеи (текущее значение по умолчанию) и гибридную композицию.

Режим гибридной композиции имеет встроенную поддержку клавиатуры, в то время как режим виртуальных дисплеев имеет некоторые проблемы с клавиатурой. Для режима гибридной композиции требуется Android SKD 19+, а для режима виртуальных дисплеев требуется Android SDK 20+
Режим гибридной композиции имеет ограничения по производительности при работе с версиями Android до Android 10, в то время как виртуальные дисплеи работают во всех поддерживаемых версиях Android. На эмуляторах разницы не заметил.

Не забудьте прописать в манифесте андроида запрос на разрешение использования интернета (пермишен) именно в главном манифесте. А то может получиться так, что на эмуляторах все работает, а на реальном устройстве нет 🙂

<uses-permission android:name=»android.permission.INTERNET»/>

Код

Я сделал две страницы, чтобы продемонстрировать два варианта загрузки страницы. Если коротко, то второй вариант себя не оправдал, так что можно рассматривать только первый. Отличия у них в коде минимальные… Ссылка на гитхаб в конце статьи.

В main ничего инициализировать не нужно, просто используем виджет WebView. Он стремится использовать большую высоту, поэтому его лучше обернуть во что-то ограничивающее. Я использовал Expanded, чтобы страница выводилась от аппбара до нижней части экрана и прокручивалась. В этом случае могут возникнуть неприятные коллизии, если над вебвью достаточно большой абзац текста. В таком случае, вероятно, самым оптимальным вариантом будет задать для webview конкретную высоту и обернуть весь экран во что-то типа SingleChildScrollView.

Веб-виджет можно использовать в Statefull виджете и в initState вызвать

if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();

если вы выберете гибридную композицию на Андроиде. Или провести эту «инициализацию» один раз где-то при запуске приложения.

Чтобы «прятать» страницу и выводить поверх нее индикатор загрузки, использую Stack, в самом «низу» которого наш вебвью.

Первое свойство виджета «initialUrl» — страница, с которой начинается вывод. С начала мне показалось, что удобнее выводить ‘about:blank’, а после того, как получаю контроллер, уже подгружать нужную. Но в итоге, отказался от этой идеи — только лишние проверки и лишнее время. Потом вообще оказалось, что это свойство не обязательное и можно выводить именно после получения контроллера. Такой вариант я реализовал на втором экране. Думал попользоваться «.timer», чтобы прерывать показ, если прошло, к примеру, секунд 10. Но «эксперимент» показал, что даже при таймауте в 1 миллисекунду, брейкпойнт там не срабатывает. Поэтому, выбираю первый способ.

И так, в параметр «ссылка инициализации» прописываем сразу страницу, которую собираемся вывести. Включаем возможность выполнения кода JavaScript (можно «внедрить» в загруженную страницу много чего интересного): javascriptMode: JavascriptMode.unrestricted. Лучше сделать это даже на статичной странице, мало ли что придется менять (я, к примеру, темную и светлую тему переключаю в пет-проекте).

Дальше важный момент: onWebViewCreated. Функция отрабатывает, когда создан WebView, и ей передается инициализированный контроллер. Нюанс, это не значит, что страница загружена. Это именно момент создания объекта (виджета). В этой функции фиксируем начало процесса, устанавливаем «флаг», что начали загружать страницу и тому подобное. Если не устанавливали initialUrl, то здесь самое время запустить loadUrl — метод контроллера (смотрите в коде второй страницы). Здесь же я устанавливаю таймер, чтобы скрыть виджет, если страница долго не загружается.

navigationDelegate отвечает за клики по ссылкам на загруженной странице. Если их не перехватывать, то переходы будут осуществляться в рамках нашего вебвью. Для моего приложения это некорректное поведение, поэтому я ловлю ссылки http:, https: и tel (есть у меня такие) и отдаю их другому плагину ( url_launcher ). В итоге по клику открывается либо браузер, либо набор телефонного номера. return NavigationDecision.prevent запрещает переход в рамках webview, а return NavigationDecision.navigate разрешает.

Четыре функции запускаются в зависимости от статуса загрузки страницы.

onPageStarted выполняется при старте загрузки страницы (получает параметр загружаемой страницы). Так как я все инициализировал все после создания виджета, то здесь ничего не делаю.

onPageFinished, понятно, что выполняется в момент, когда страница так или иначе загружена (даже если получили ошибку, к примеру, нет интернета). В этой функции я устанавливаю флаг isHtmlLoaded и отменяю таймер, при необходимости.

onProgress запускается несколько раз во время загрузки. Возникает соблазн, с ее помощью выводить более информативный индикатор загрузки, но… Поэкспериментируйте сами или посмотрите скриншоты ниже. Процент загрузки выводится очень странно. Еще до старта может выдать 10 или 20. После завершения может выдать не сразу 100. А бывает, что 100 выводит несколько раз. В общем, я эту функцию не использую.

Очень нужный колбэк onWebResourceError, отрабатывает в случае ошибок. Я пытался отловить систему, в каком случае и что выводится… так и не выстроил чего-то нормального, поэтому просто устанавливаю флаг ошибки, чтобы вебвью прикрылось пустым контейнером и не отсвечивало.

Пример, как отрабатывали print`ы в функциях, ниже (вариант, когда я отключил интернет на эмуляторе):

Если нет интернета…

Дальше по стэку идет контейнер, который прикрывает «страницу», когда возникает ошибка, иначе пользователь увидит не хорошую «картинку». Выводится этот контейнер, когда установлен флаг ошибки или страница в процессе загрузки.

Пара вспомогательных виджетов еще сверху: круговой индикатор и фраза о том, что процесс идет 🙂 Выводятся, когда страница загружается. И еще «выше» надпись про ошибку и кнопка для перезапуска-перезагрузки страницы.

В общем-то, все достаточно просто. Не хватает более прозрачной отработки ошибок, но, возможно, это все из-за эмулятора и нужно поэкспериментировать на реальном устройстве.

С помощью полученного контролера мы можем «управлять» страницей: запускать на ней Javascript-функции, получать заголовок и тому подобное. Если мы не запрещали переходы, то возвращаться на предыдущую страницу (простейший свой браузер, типа).

Ссылка на репозиторий здесь.

Еще примеры, как отрабатывают функции

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *