DevGang
Авторизоваться

Как создать собственный коммуникационный мост во 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:

Иерархия файлов после создания каталога asses<br>
Иерархия файлов после создания каталога asses

Затем создайте index.html в каталоге assets и добавьте следующий код:

index.html
<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:

  1. fromFlutter это метод, который мы будем вызывать из Flutter со строкой, представляющей новый заголовок для страницы
  2. 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, показанного выше:

  1. initialUrl здесь мы можем определить, на какой URL указывает WebView. Здесь мы решили ни на что не указывать, поскольку собираемся загрузить наш локальный HTML-файл.
  2. 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, чтобы мы могли отображать панель.

Вы можете получить исходный код приложения, описанного в этой статье ниже:

Источник:

#Flutter
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться