Как создать собственный коммуникационный мост во Flutter с помощью WebView и JavaScript
Хотя может показаться, что это несложное дело, вы скоро поймете, что для того, чтобы эта функция заработала, потребуется немного поработать.
Прежде всего, важно понимать, что (на момент написания этой статьи) Flutter не имеет встроенной поддержки WebView.
В отличие от собственного приложения в Kotlin или Swift, где вы можете просто создать экземпляр компонента WebView, вы не можете добавить компонент WebView в ваше приложение Flutter из коробки.
В этой статье мы рассмотрим, как настроить WebView в приложениях Flutter и как взаимодействовать между Flutter и Webview.
Как настроить WebView в приложении Flutter
После создания нового проекта Flutter нам нужно использовать пакет webview_flutter, чтобы иметь возможность использовать WebView. Мы добавим зависимость в наш файл pubspec.yaml
:
dependencies:
flutter:
sdk: flutter
webview_flutter: ^1.0.7
Затем нам нужно запустить в терминале pub get
:
flutter pub get
Далее мы импортируем пакет в наш файл main.dart
:
import 'package:webview_flutter/webview_flutter.dart';
Если вы еще не очистили код начального проекта, сейчас хорошее время для этого.
После того, как вы удалите все комментарии, плавающую кнопку действия и все, что с ней связано, у вас останется это (я добавил текстовый виджет только для показа):
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Communication Bridge',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Native - JS Communication Bridge'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
WebViewController _controller;
@override
Widget build(BuildContext context) {
return Text(
"Flutter JS-Native Communication Bridge"
);
}
}
Что даст вам такой результат:
Создание локального ресурса HTML
Поскольку мы будем использовать локальный HTML-файл со встроенным в него кодом JavaScript, нам необходимо создать его в нашем проекте.
Все локальные ресурсы в приложении Flutter должны находиться в каталоге assets
.
Создайте каталог assets
в своей основной иерархии проекта, щелкнув правой кнопкой мыши на левой боковой панели и выбрав New → Directory:
Затем создайте index.html
в каталоге assets и добавьте следующий код:
<html>
<head>
<title>My Local HTML File</title>
</head>
<body>
<h1 id="title">Hello World!</h1>
<script type="text/javascript">
function fromFlutter(newTitle) {
document.getElementById("title").innerHTML = newTitle;
sendBack();
}
function sendBack() {
messageHandler.postMessage("Hello from JS");
}
</script>
</body>
</html>
Вы заметите, что мы написали два метода в разделе JavaScript нашего html:
fromFlutter
это метод, который мы будем вызывать из Flutter со строкой, представляющей новый заголовок для страницыsendBack
это метод, который мы будем вызывать для обратной связи с Flutter. В нем мы отправляем строковое сообщение.
Мы перейдем к содержимому sendBack через минуту, но перед этим мы должны настроить наш WebView в нашем приложении.
✋ Не забудьте добавить index.html
к вашему pubspec.yaml
в секции assets
(используйте правильный отступ):
dependencies:
flutter:
sdk: flutter
webview_flutter: ^1.0.7
cupertino_icons: ^1.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
assets:
- assets/index.html
Настроить WebView
Поскольку мы уже импортировали пакет в наш файл main.dart
, нам нужно заменить виджет Text на виджет WebView:
class _MyHomePageState extends State<MyHomePage> {
WebViewController _controller;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Webview')),
body: WebView(
initialUrl: 'about:blank',
onWebViewCreated: (WebViewController webviewController) {
_controller = webviewController;
_loadHtmlFromAssets();
},
),
);
}
_loadHtmlFromAssets() async {
String file = await rootBundle.loadString('assets/index.html');
_controller.loadUrl(Uri.dataFromString(
file,
mimeType: 'text/html',
encoding: Encoding.getByName('utf-8')).toString());
}
}
Мы обернули наш WebView виджетом Scaffold (мы рассмотрим это подробнее позже в статье), но давайте сосредоточимся на различных полях виджета WebView, показанного выше:
initialUrl
здесь мы можем определить, на какой URL указывает WebView. Здесь мы решили ни на что не указывать, поскольку собираемся загрузить наш локальный HTML-файл.onWebViewCreated
- это обратный вызов, который мы получаем от пакета после создания WebView. Поскольку мы хотим сохранить экземпляр контроллера, полученный в результате этого обратного вызова, мы создали приватный экземпляр_controller
для его хранения
Вы также заметите, что мы создали вызываемый метод _loadHtmlFromAssets
, который, как следует из его имени, загрузит наш локальный HTML-файл в WebView.
Внутри этого метода мы используем наш приватный экземпляр WebViewController _controller
, и его публичный метод loadUrl
для загрузки нашего локального файла HTML. Из-за логики этого метода его выполнение асинхронно.
Если мы запустим наше приложение, мы получим следующее:
Как общаться между Flutter и WebView
Теперь давайте добавим некоторые функции для вызова метода fromFlutter
, который мы определили в нашем локальном HTML-файле.
Для этого мы добавим плавающую кнопку действия (или FAB) в наш макет и подключим ее метод onPressed
для вызова метода fromFlutter
.
Это также причина использования виджета Scaffold, поэтому мы можем легко добавить FAB:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Webview')),
body: WebView(
initialUrl: 'about:blank',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webviewController) {
_controller = webviewController;
_loadHtmlFromAssets();
},
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.arrow_upward),
onPressed: () {
_controller.evaluateJavascript('fromFlutter("From Flutter")');
},
),
);
}
Чтобы выполнять вызовы из Flutter в загруженный HTML-код, мы используем метод evaluateJavascript
. Чтобы использовать его, мы должны добавить еще одно свойство в наш WebView с именем javascriptMode
.
В приведенном выше коде мы устанавливаем unrestricted. Если мы не установим его, мы не сможем общаться между Flutter и WebView:
Как передать обратную связь от WebView к Flutter
Помните, как я сказал, что мы поговорим о содержании нашего метода sendBack
? Сделаем это сейчас:
function sendBack() {
messageHandler.postMessage("Hello from JS");
}
В методе sendBack
мы используем объект с именем messageHandler
, и вызываем у него метод postMessage
.
Как и при создании коммуникационного моста в собственном приложении, после его настройки вы добавляете объект к глобальному объекту Window
на уровне JavaScript, который будет использоваться для связи.
Вы можете присвоить этому объекту любое имя, если вы ссылаетесь на него, когда делаете вызовы из JavaScript в собственное приложение.
Вы можете спросить, как этот объект добавлен к уровню JavaScript в нашем приложении? Добавив атрибут JavascriptChannels
в наш виджет WebView:
class _MyHomePageState extends State<MyHomePage> {
WebViewController _controller;
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(title: Text('Webview')),
body: WebView(
initialUrl: 'about:blank',
javascriptMode: JavascriptMode.unrestricted,
javascriptChannels: Set.from([
JavascriptChannel(
name: 'messageHandler',
onMessageReceived: (JavascriptMessage message) {
_scaffoldKey.currentState.showSnackBar(
SnackBar(
content: Text(message)
)
);
})
]),
onWebViewCreated: (WebViewController webviewController) {
_controller = webviewController;
_loadHtmlFromAssets();
},
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.arrow_upward),
onPressed: () {
_controller.evaluateJavascript('fromFlutter("From Flutter")');
},
),
);
}
Мы определили объект JavascriptChannel
с именем и обработчиком onMessageReceived
. Имя, которое мы дали этому каналу messageHandler
- это имя, которое мы используем для связи из локального файла HTML, который мы загрузили на наш собственный уровень.
Для зорких вы наверняка заметили, что была добавлена новая приватная переменная _scaffoldKey
. Это связано с тем, что нам нужно добавить ключ к нашему виджету Scaffold, чтобы мы могли отображать панель.
Вы можете получить исходный код приложения, описанного в этой статье ниже:
A repository that contains code associated with various Medium articles I have written - TomerPacific/MediumArticles