Добавление WebView в ваше приложение Flutter

1. Введение

Последнее обновление: 19 октября 2021 г.

С помощью плагина WebView Flutter вы можете добавить виджет WebView в свое приложение Flutter для Android или iOS. В iOS виджет WebView поддерживается WKWebView , а в Android виджет WebView поддерживается WebView . Плагин может отображать виджеты Flutter поверх веб-представления. Например, можно отобразить раскрывающееся меню поверх веб-представления.

Что ты построишь

В этой лаборатории кода вы шаг за шагом создадите мобильное приложение с WebView с использованием Flutter SDK. Ваше приложение будет:

  • Отображение веб-контента в WebView
  • Отображать виджеты Flutter, расположенные поверх WebView
  • Реагировать на события прогресса загрузки страницы
  • Управляйте WebView через WebViewController
  • Блокируйте веб-сайты с помощью NavigationDelegate
  • Оценка выражений JavaScript
  • Обработка обратных вызовов из JavaScript с помощью JavascriptChannels
  • Установить, удалить, добавить или показать файлы cookie
  • Загружать и отображать HTML из ресурсов, файлов или строк, содержащих HTML.

Снимок экрана симулятора iPhone, на котором запущено приложение Flutter со встроенным веб-представлением, показывающим домашнюю страницу Flutter.dev.

Снимок экрана эмулятора Android, на котором запущено приложение Flutter со встроенным веб-представлением, показывающим домашнюю страницу Flutter.dev.

Что вы узнаете

В этой лаборатории вы узнаете, как использовать плагин webview_flutter различными способами, в том числе:

  • Как настроить плагин webview_flutter
  • Как прослушивать события хода загрузки страницы
  • Как управлять навигацией по страницам
  • Как дать команду WebView перемещаться вперед и назад по своей истории
  • Как оценить JavaScript, в том числе используя возвращаемые результаты
  • Как зарегистрировать обратные вызовы для вызова кода Dart из JavaScript
  • Как управлять файлами cookie
  • Как загружать и отображать HTML-страницы из ресурсов или файлов или строки, содержащей HTML.

Что вам понадобится

2. Настройте среду разработки Flutter.

Для выполнения этой лабораторной работы вам понадобятся два программного обеспечения — Flutter SDK и редактор .

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

  • Физическое устройство Android или iOS , подключенное к вашему компьютеру и переведенное в режим разработчика.
  • Симулятор iOS (требуется установка инструментов Xcode).
  • Эмулятор Android (требуется установка в Android Studio).

3. Начало работы

Начало работы с Flutter

Существует множество способов создания нового проекта Flutter, причем инструменты для этой задачи предоставляют как Android Studio , так и Visual Studio Code . Либо следуйте связанным процедурам для создания проекта, либо выполните следующие команды в удобном терминале командной строки.

$ flutter create --platforms=android,ios webview_in_flutter
Creating project webview_in_flutter...
Resolving dependencies in `webview_in_flutter`... 
Downloading packages... 
Got dependencies in `webview_in_flutter`.
Wrote 74 files.

All done!
You can find general documentation for Flutter at: https://docs.flutter.dev/
Detailed API documentation is available at: https://api.flutter.dev/
If you prefer video documentation, consider: https://www.youtube.com/c/flutterdev

In order to run your application, type:

  $ cd webview_in_flutter
  $ flutter run

Your application code is in webview_in_flutter/lib/main.dart.

Добавление плагина WebView Flutter в качестве зависимости

Добавить дополнительные возможности в приложение Flutter легко с помощью пакетов Pub . В этой лаборатории кода вы добавите плагин webview_flutter в свой проект. Выполните следующие команды в терминале.

$ cd webview_in_flutter
$ flutter pub add webview_flutter
Resolving dependencies...
Downloading packages...
  collection 1.18.0 (1.19.0 available)
  leak_tracker 10.0.5 (10.0.7 available)
  leak_tracker_flutter_testing 3.0.5 (3.0.7 available)
  material_color_utilities 0.11.1 (0.12.0 available)
+ plugin_platform_interface 2.1.8
  string_scanner 1.2.0 (1.3.0 available)
  test_api 0.7.2 (0.7.3 available)
+ webview_flutter 4.9.0
+ webview_flutter_android 3.16.7
+ webview_flutter_platform_interface 2.10.0
+ webview_flutter_wkwebview 3.15.0
Changed 5 dependencies!
6 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.

Если вы проверите свой pubspec.yaml , вы увидите, что в разделе зависимостей есть строка для плагина webview_flutter .

Настройка Android minSDK

Чтобы использовать плагин webview_flutter на Android, вам необходимо установить minSDK значение 20 . Измените файл android/app/build.gradle следующим образом:

Android/приложение/build.gradle

android {
    //...

    defaultConfig {
        applicationId = "com.example.webview_in_flutter"
        minSdk = 20                                         // Modify this line
        targetSdk = flutter.targetSdkVersion
        versionCode = flutterVersionCode.toInteger()
        versionName = flutterVersionName
    }

4. Добавление виджета WebView в приложение Flutter.

На этом этапе вы добавите WebView в свое приложение. WebViews — это размещенные собственные представления, и вы, как разработчик приложения, можете выбрать, как разместить эти собственные представления в своем приложении. На Android у вас есть выбор между виртуальными дисплеями (в настоящее время используется по умолчанию для Android) и гибридной композицией. Однако iOS всегда использует гибридную композицию.

Для более подробного обсуждения различий между виртуальными дисплеями и гибридной композицией прочтите документацию по размещению собственных представлений Android и iOS в вашем приложении Flutter с представлениями платформы .

Вывод веб-просмотра на экран

Замените содержимое lib/main.dart следующим:

библиотека/main.dart

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

void main() {
  runApp(
    MaterialApp(
      theme: ThemeData(useMaterial3: true),
      home: const WebViewApp(),
    ),
  );
}

class WebViewApp extends StatefulWidget {
  const WebViewApp({super.key});

  @override
  State<WebViewApp> createState() => _WebViewAppState();
}

class _WebViewAppState extends State<WebViewApp> {
  late final WebViewController controller;

  @override
  void initState() {
    super.initState();
    controller = WebViewController()
      ..loadRequest(
        Uri.parse('https://flutter.dev'),
      );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter WebView'),
      ),
      body: WebViewWidget(
        controller: controller,
      ),
    );
  }
}

Запуск этого на iOS или Android отобразит WebView как полноэкранное окно браузера на вашем устройстве, что означает, что браузер отображается на вашем устройстве в полноэкранном режиме без каких-либо границ или полей. По мере прокрутки вы заметите части страницы, которые могут выглядеть немного странно. Это связано с тем, что JavaScript в настоящее время отключен, а для правильного рендеринга flutter.dev требуется JavaScript.

Запуск приложения

Запустите приложение Flutter на iOS или Android, чтобы увидеть веб-просмотр, в котором отображается веб-сайт flutter.dev . Альтернативно запустите приложение в эмуляторе Android или симуляторе iOS. Не стесняйтесь заменять первоначальный URL-адрес WebView, например, своим собственным веб-сайтом.

$ flutter run

Предполагая, что у вас запущен соответствующий симулятор или эмулятор или подключено физическое устройство, после компиляции и развертывания приложения на вашем устройстве вы должны увидеть что-то вроде следующего:

Снимок экрана симулятора iPhone, на котором запущено приложение Flutter со встроенным веб-представлением, показывающим домашнюю страницу Flutter.dev.

Снимок экрана эмулятора Android, на котором запущено приложение Flutter со встроенным веб-представлением, показывающим домашнюю страницу Flutter.dev.

5. Прослушивание событий загрузки страницы

Виджет WebView предоставляет несколько событий хода загрузки страницы, которые ваше приложение может прослушивать. Во время цикла загрузки страницы WebView запускаются три различных события загрузки страницы: onPageStarted , onProgress и onPageFinished . На этом этапе вы реализуете индикатор загрузки страницы. В качестве бонуса это покажет, что вы можете отображать контент Flutter в области содержимого WebView .

Добавление событий загрузки страницы в ваше приложение

Создайте новый исходный файл lib/src/web_view_stack.dart и заполните его следующим содержимым:

lib/src/web_view_stack.dart

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class WebViewStack extends StatefulWidget {
  const WebViewStack({super.key});

  @override
  State<WebViewStack> createState() => _WebViewStackState();
}

class _WebViewStackState extends State<WebViewStack> {
  var loadingPercentage = 0;
  late final WebViewController controller;

  @override
  void initState() {
    super.initState();
    controller = WebViewController()
      ..setNavigationDelegate(NavigationDelegate(
        onPageStarted: (url) {
          setState(() {
            loadingPercentage = 0;
          });
        },
        onProgress: (progress) {
          setState(() {
            loadingPercentage = progress;
          });
        },
        onPageFinished: (url) {
          setState(() {
            loadingPercentage = 100;
          });
        },
      ))
      ..loadRequest(
        Uri.parse('https://flutter.dev'),
      );
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        WebViewWidget(
          controller: controller,
        ),
        if (loadingPercentage < 100)
          LinearProgressIndicator(
            value: loadingPercentage / 100.0,
          ),
      ],
    );
  }
}

Этот код обернул виджет WebView в Stack , условно наложив на WebView индикатор LinearProgressIndicator , когда процент загрузки страницы меньше 100%. Поскольку это касается состояния программы, которое меняется со временем, вы сохранили это состояние в классе State , связанном с StatefulWidget .

Чтобы использовать этот новый виджет WebViewStack , измените файл lib/main.dart следующим образом:

библиотека/main.dart

import 'package:flutter/material.dart';

import 'src/web_view_stack.dart';

void main() {
  runApp(
    MaterialApp(
      theme: ThemeData(useMaterial3: true),
      home: const WebViewApp(),
    ),
  );
}

class WebViewApp extends StatefulWidget {
  const WebViewApp({super.key});

  @override
  State<WebViewApp> createState() => _WebViewAppState();
}

class _WebViewAppState extends State<WebViewApp> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter WebView'),
      ),
      body: const WebViewStack(),
    );
  }
}

Когда вы запускаете приложение, в зависимости от условий вашей сети и того, кэширует ли браузер страницу, на которую вы переходите, вы увидите индикатор загрузки страницы, наложенный поверх области содержимого WebView .

6. Работа с WebViewController

Доступ к WebViewController из виджета WebView

Виджет WebView обеспечивает программное управление с помощью WebViewController . Этот контроллер становится доступным после создания виджета WebView посредством обратного вызова. Асинхронный характер доступности этого контроллера делает его главным кандидатом на роль асинхронного класса Completer<T> Dart.

Обновите lib/src/web_view_stack.dart следующим образом:

lib/src/web_view_stack.dart

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class WebViewStack extends StatefulWidget {
  const WebViewStack({required this.controller, super.key}); // MODIFY

  final WebViewController controller;                        // ADD

  @override
  State<WebViewStack> createState() => _WebViewStackState();
}

class _WebViewStackState extends State<WebViewStack> {
  var loadingPercentage = 0;
  // REMOVE the controller that was here

  @override
  void initState() {
    super.initState();
    // Modify from here...
    widget.controller.setNavigationDelegate(
      NavigationDelegate(
        onPageStarted: (url) {
          setState(() {
            loadingPercentage = 0;
          });
        },
        onProgress: (progress) {
          setState(() {
            loadingPercentage = progress;
          });
        },
        onPageFinished: (url) {
          setState(() {
            loadingPercentage = 100;
          });
        },
      ),
    );
    // ...to here.
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        WebViewWidget(
          controller: widget.controller,                     // MODIFY
        ),
        if (loadingPercentage < 100)
          LinearProgressIndicator(
            value: loadingPercentage / 100.0,
          ),
      ],
    );
  }
}

Виджет WebViewStack теперь использует контроллер, созданный в окружающем виджете. Это позволит легко использовать контроллер WebViewWidget в других частях приложения.

Создание элементов управления навигацией

Иметь работающий WebView — это одно, но возможность перемещаться вперед и назад по истории страницы и перезагружать страницу была бы полезным набором дополнений. К счастью, с помощью WebViewController вы можете добавить эту функциональность в свое приложение.

Создайте новый исходный файл lib/src/navigation_controls.dart и заполните его следующим:

lib/src/navigation_controls.dart

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class NavigationControls extends StatelessWidget {
  const NavigationControls({required this.controller, super.key});

  final WebViewController controller;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        IconButton(
          icon: const Icon(Icons.arrow_back_ios),
          onPressed: () async {
            final messenger = ScaffoldMessenger.of(context);
            if (await controller.canGoBack()) {
              await controller.goBack();
            } else {
              messenger.showSnackBar(
                const SnackBar(content: Text('No back history item')),
              );
              return;
            }
          },
        ),
        IconButton(
          icon: const Icon(Icons.arrow_forward_ios),
          onPressed: () async {
            final messenger = ScaffoldMessenger.of(context);
            if (await controller.canGoForward()) {
              await controller.goForward();
            } else {
              messenger.showSnackBar(
                const SnackBar(content: Text('No forward history item')),
              );
              return;
            }
          },
        ),
        IconButton(
          icon: const Icon(Icons.replay),
          onPressed: () {
            controller.reload();
          },
        ),
      ],
    );
  }
}

Этот виджет использует WebViewController , предоставленный ему во время создания, чтобы позволить пользователю управлять WebView с помощью серии IconButton .

Добавление элементов управления навигацией в AppBar

Имея в руках обновленный WebViewStack и недавно созданные NavigationControls , настало время объединить все это в обновленном WebViewApp . Здесь мы создаем общий WebViewController . Поскольку WebViewApp находится в верхней части дерева виджетов в этом приложении, имеет смысл создать его на этом уровне.

Обновите файл lib/main.dart следующим образом:

библиотека/main.dart

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';  // ADD

import 'src/navigation_controls.dart';                  // ADD
import 'src/web_view_stack.dart';

void main() {
  runApp(
    MaterialApp(
      theme: ThemeData(useMaterial3: true),
      home: const WebViewApp(),
    ),
  );
}

class WebViewApp extends StatefulWidget {
  const WebViewApp({super.key});

  @override
  State<WebViewApp> createState() => _WebViewAppState();
}

class _WebViewAppState extends State<WebViewApp> {
  // Add from here...
  late final WebViewController controller;

  @override
  void initState() {
    super.initState();
    controller = WebViewController()
      ..loadRequest(
        Uri.parse('https://flutter.dev'),
      );
  }
  // ...to here.

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter WebView'),
        // Add from here...
        actions: [
          NavigationControls(controller: controller),
        ],
        // ...to here.
      ),
      body: WebViewStack(controller: controller),       // MODIFY
    );
  }
}

При запуске приложения должна открыться веб-страница с элементами управления:

Снимок экрана симулятора iPhone, на котором запущено приложение Flutter со встроенным веб-представлением, показывающим домашнюю страницу Flutter.dev с элементами управления предыдущей, следующей страницей и перезагрузкой страницы.

Снимок экрана эмулятора Android, на котором запущено приложение Flutter со встроенным веб-представлением, показывающим домашнюю страницу Flutter.dev с элементами управления предыдущей, следующей страницей и перезагрузкой страницы.

7. Отслеживание навигации с помощью NavigationDelegate

WebView предоставляет вашему приложению NavigationDelegate, который позволяет вашему приложению отслеживать и управлять навигацией по страницам виджета WebView . Когда навигация инициируется WebView, например, когда пользователь нажимает ссылку, вызывается NavigationDelegate . Обратный вызов NavigationDelegate можно использовать для управления тем, продолжает ли WebView навигацию.

Зарегистрируйте собственный NavigationDelegate

На этом этапе вы зарегистрируете обратный вызов NavigationDelegate , чтобы заблокировать переход на YouTube.com . Обратите внимание: эта упрощенная реализация также блокирует встроенный контент YouTube, который появляется на различных страницах документации Flutter API.

Обновите lib/src/web_view_stack.dart следующим образом:

lib/src/web_view_stack.dart

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class WebViewStack extends StatefulWidget {
  const WebViewStack({required this.controller, super.key});

  final WebViewController controller;

  @override
  State<WebViewStack> createState() => _WebViewStackState();
}

class _WebViewStackState extends State<WebViewStack> {
  var loadingPercentage = 0;

  @override
  void initState() {
    super.initState();
    widget.controller.setNavigationDelegate(
      NavigationDelegate(
        onPageStarted: (url) {
          setState(() {
            loadingPercentage = 0;
          });
        },
        onProgress: (progress) {
          setState(() {
            loadingPercentage = progress;
          });
        },
        onPageFinished: (url) {
          setState(() {
            loadingPercentage = 100;
          });
        },
        // Add from here...
        onNavigationRequest: (navigation) {
          final host = Uri.parse(navigation.url).host;
          if (host.contains('youtube.com')) {
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(
                content: Text(
                  'Blocking navigation to $host',
                ),
              ),
            );
            return NavigationDecision.prevent;
          }
          return NavigationDecision.navigate;
        },
        // ...to here.
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        WebViewWidget(
          controller: widget.controller,
        ),
        if (loadingPercentage < 100)
          LinearProgressIndicator(
            value: loadingPercentage / 100.0,
          ),
      ],
    );
  }
}

На следующем шаге вы добавите пункт меню, позволяющий тестировать NavigationDelegate с помощью класса WebViewController . Читателю остается в качестве упражнения расширить логику обратного вызова, чтобы блокировать только полностраничную навигацию на YouTube.com и по-прежнему разрешать встроенный контент YouTube в документации API.

8. Добавление кнопки меню в AppBar

В течение следующих нескольких шагов вы создадите кнопку меню в виджете AppBar , которая будет использоваться для оценки JavaScript, вызова каналов JavaScript и управления файлами cookie. В общем, действительно полезное меню.

Создайте новый исходный файл lib/src/menu.dart и заполните его следующим:

lib/src/menu.dart

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

enum _MenuOptions {
  navigationDelegate,
}

class Menu extends StatelessWidget {
  const Menu({required this.controller, super.key});

  final WebViewController controller;

  @override
  Widget build(BuildContext context) {
    return PopupMenuButton<_MenuOptions>(
      onSelected: (value) async {
        switch (value) {
          case _MenuOptions.navigationDelegate:
            await controller.loadRequest(Uri.parse('https://youtube.com'));
        }
      },
      itemBuilder: (context) => [
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.navigationDelegate,
          child: Text('Navigate to YouTube'),
        ),
      ],
    );
  }
}

Когда пользователь выбирает пункт меню «Перейти к YouTube» , выполняется метод loadRequest WebViewController . Эта навигация будет заблокирована обратным вызовом navigationDelegate , который вы создали на предыдущем шаге.

Чтобы добавить меню на экран WebViewApp , измените lib/main.dart следующим образом:

библиотека/main.dart

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

import 'src/menu.dart';                               // ADD
import 'src/navigation_controls.dart';
import 'src/web_view_stack.dart';

void main() {
  runApp(
    MaterialApp(
      theme: ThemeData(useMaterial3: true),
      home: const WebViewApp(),
    ),
  );
}

class WebViewApp extends StatefulWidget {
  const WebViewApp({super.key});

  @override
  State<WebViewApp> createState() => _WebViewAppState();
}

class _WebViewAppState extends State<WebViewApp> {
  late final WebViewController controller;

  @override
  void initState() {
    super.initState();
    controller = WebViewController()
      ..loadRequest(
        Uri.parse('https://flutter.dev'),
      );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter WebView'),
        actions: [
          NavigationControls(controller: controller),
          Menu(controller: controller),               // ADD
        ],
      ),
      body: WebViewStack(controller: controller),
    );
  }
}

Запустите приложение и нажмите пункт меню «Перейти на YouTube» . Вас должен встретить SnackBar, сообщающий, что навигационный контроллер заблокировал переход на YouTube.

Снимок экрана эмулятора Android, на котором запущено приложение Flutter со встроенным веб-просмотром, на котором показана домашняя страница Flutter.dev с пунктом меню, показывающим опцию «Перейти на YouTube».

Снимок экрана эмулятора Android, на котором запущено приложение Flutter со встроенным веб-представлением, на котором показана домашняя страница Flutter.dev со всплывающим всплывающим сообщением «Блокировка навигации по m.youtube.com».

9. Оценка JavaScript

WebViewController может оценивать выражения JavaScript в контексте текущей страницы. Существует два разных способа оценки JavaScript: для кода JavaScript, который не возвращает значение, используйте runJavaScript , а для кода JavaScript, который возвращает значение, используйте runJavaScriptReturningResult .

Чтобы включить JavaScript, вам необходимо настроить WebViewController , указав для свойства javaScriptMode значение JavascriptMode.unrestricted . По умолчанию javascriptMode установлено значение JavascriptMode.disabled .

Обновите класс _WebViewStackState , добавив параметр javascriptMode следующим образом:

lib/src/web_view_stack.dart

class _WebViewStackState extends State<WebViewStack> {
  var loadingPercentage = 0;

  @override
  void initState() {
    super.initState();
    widget.controller
      ..setNavigationDelegate(              // Modify this line to use .. instead of .
        NavigationDelegate(
          onPageStarted: (url) {
            setState(() {
              loadingPercentage = 0;
            });
          },
          onProgress: (progress) {
            setState(() {
              loadingPercentage = progress;
            });
          },
          onPageFinished: (url) {
            setState(() {
              loadingPercentage = 100;
            });
          },
          onNavigationRequest: (navigation) {
            final host = Uri.parse(navigation.url).host;
            if (host.contains('youtube.com')) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(
                  content: Text(
                    'Blocking navigation to $host',
                  ),
                ),
              );
              return NavigationDecision.prevent;
            }
            return NavigationDecision.navigate;
          },
        ),
      )
      ..setJavaScriptMode(JavaScriptMode.unrestricted);        // Add this line
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        WebViewWidget(
          controller: widget.controller,
        ),
        if (loadingPercentage < 100)
          LinearProgressIndicator(
            value: loadingPercentage / 100.0,
          ),
      ],
    );
  }
}

Теперь, когда WebViewWidget может выполнять JavaScript, вы можете добавить в меню опцию для использования метода runJavaScriptReturningResult .

Используя редактор или клавиатуру, преобразуйте класс Menu в StatefulWidget. Измените lib/src/menu.dart так, чтобы он соответствовал следующему:

lib/src/menu.dart

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

enum _MenuOptions {
  navigationDelegate,
  userAgent,                                              // Add this line
}

class Menu extends StatefulWidget {                       // Convert to StatefulWidget
  const Menu({required this.controller, super.key});

  final WebViewController controller;

  @override                                               // Add from here
  State<Menu> createState() => _MenuState();
}

class _MenuState extends State<Menu> {                    // To here.
  @override
  Widget build(BuildContext context) {
    return PopupMenuButton<_MenuOptions>(
      onSelected: (value) async {
        switch (value) {
          case _MenuOptions.navigationDelegate:           // Modify from here
            await widget.controller
                .loadRequest(Uri.parse('https://youtube.com'));
          case _MenuOptions.userAgent:
            final userAgent = await widget.controller
                .runJavaScriptReturningResult('navigator.userAgent');
            if (!context.mounted) return;
            ScaffoldMessenger.of(context).showSnackBar(SnackBar(
              content: Text('$userAgent'),
            ));                                           // To here.
        }
      },
      itemBuilder: (context) => [
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.navigationDelegate,
          child: Text('Navigate to YouTube'),
        ),
        const PopupMenuItem<_MenuOptions>(                // Add from here
          value: _MenuOptions.userAgent,
          child: Text('Show user-agent'),
        ),                                                // To here.
      ],
    );
  }
}

Когда вы нажимаете на пункт меню «Показать пользовательский агент», результат выполнения выражения JavaScript navigator.userAgent отображается в Snackbar . При запуске приложения вы можете заметить, что страница Flutter.dev выглядит по-другому. Это результат работы с включенным JavaScript.

Снимок экрана симулятора iPhone, на котором запущено приложение Flutter со встроенным веб-представлением, на котором показана домашняя страница Flutter.dev с пунктами меню, показывающими параметры «Перейти на YouTube» или «Показать пользовательский агент».

Снимок экрана симулятора iPhone, на котором запущено приложение Flutter со встроенным веб-представлением, на котором показана домашняя страница Flutter.dev со всплывающим всплывающим сообщением, показывающим строку пользовательского агента.

10. Работа с каналами JavaScript

Каналы Javascript позволяют вашему приложению регистрировать обработчики обратного вызова в контексте JavaScript WebViewWidget , которые можно вызывать для передачи значений обратно в код Dart приложения. На этом этапе вы зарегистрируете канал SnackBar , который будет вызываться с результатом XMLHttpRequest .

Обновите класс WebViewStack следующим образом:

lib/src/web_view_stack.dart

class WebViewStack extends StatefulWidget {
  const WebViewStack({required this.controller, super.key});

  final WebViewController controller;

  @override
  State<WebViewStack> createState() => _WebViewStackState();
}

class _WebViewStackState extends State<WebViewStack> {
  var loadingPercentage = 0;

  @override
  void initState() {
    super.initState();
    widget.controller
      ..setNavigationDelegate(
        NavigationDelegate(
          onPageStarted: (url) {
            setState(() {
              loadingPercentage = 0;
            });
          },
          onProgress: (progress) {
            setState(() {
              loadingPercentage = progress;
            });
          },
          onPageFinished: (url) {
            setState(() {
              loadingPercentage = 100;
            });
          },
          onNavigationRequest: (navigation) {
            final host = Uri.parse(navigation.url).host;
            if (host.contains('youtube.com')) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(
                  content: Text(
                    'Blocking navigation to $host',
                  ),
                ),
              );
              return NavigationDecision.prevent;
            }
            return NavigationDecision.navigate;
          },
        ),
      )
      // Modify from here...
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..addJavaScriptChannel(
        'SnackBar',
        onMessageReceived: (message) {
          ScaffoldMessenger.of(context)
              .showSnackBar(SnackBar(content: Text(message.message)));
        },
      );
      // ...to here.
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        WebViewWidget(
          controller: widget.controller,
        ),
        if (loadingPercentage < 100)
          LinearProgressIndicator(
            value: loadingPercentage / 100.0,
          ),
      ],
    );
  }
}

Для каждого канала JavaScript в Set объект канала становится доступным в контексте JavaScript как свойство окна с тем же именем, что и name канала JavaScript. Использование этого из контекста JavaScript включает вызов postMessage на канале JavaScript для отправки сообщения, которое передается обработчику обратного вызова onMessageReceived именованного JavascriptChannel .

Чтобы использовать добавленный выше канал Javascript, добавьте еще один пункт меню, который выполняет XMLHttpRequest в контексте JavaScript и передает результаты обратно с помощью канала JavaScript SnackBar .

Теперь, когда WebViewWidget знает о наших каналах JavaScript , вы добавите пример для дальнейшего расширения приложения. Для этого добавьте дополнительный PopupMenuItem в класс Menu и добавьте дополнительную функциональность.

Обновите _MenuOptions добавив дополнительную опцию меню, добавив значение перечисления javascriptChannel , и добавьте реализацию в класс Menu следующим образом:

lib/src/menu.dart

enum _MenuOptions {
  navigationDelegate,
  userAgent,
  javascriptChannel,                                      // Add this option
}

class Menu extends StatefulWidget {
  const Menu({required this.controller, super.key});

  final WebViewController controller;

  @override
  State<Menu> createState() => _MenuState();
}

class _MenuState extends State<Menu> {
  @override
  Widget build(BuildContext context) {
    return PopupMenuButton<_MenuOptions>(
      onSelected: (value) async {
        switch (value) {
          case _MenuOptions.navigationDelegate:
            await widget.controller
                .loadRequest(Uri.parse('https://youtube.com'));
          case _MenuOptions.userAgent:
            final userAgent = await widget.controller
                .runJavaScriptReturningResult('navigator.userAgent');
            if (!context.mounted) return;
            ScaffoldMessenger.of(context).showSnackBar(SnackBar(
              content: Text('$userAgent'),
            ));
          case _MenuOptions.javascriptChannel:            // Add from here
            await widget.controller.runJavaScript('''
var req = new XMLHttpRequest();
req.open('GET', "https://api.ipify.org/?format=json");
req.onload = function() {
  if (req.status == 200) {
    let response = JSON.parse(req.responseText);
    SnackBar.postMessage("IP Address: " + response.ip);
  } else {
    SnackBar.postMessage("Error: " + req.status);
  }
}
req.send();''');                                          // To here.
        }
      },
      itemBuilder: (context) => [
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.navigationDelegate,
          child: Text('Navigate to YouTube'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.userAgent,
          child: Text('Show user-agent'),
        ),
        const PopupMenuItem<_MenuOptions>(                // Add from here
          value: _MenuOptions.javascriptChannel,
          child: Text('Lookup IP Address'),
        ),                                                // To here.
      ],
    );
  }
}

Этот код JavaScript выполняется, когда пользователь выбирает пункт меню «Пример канала JavaScript» .

var req = new XMLHttpRequest();
req.open('GET', "https://api.ipify.org/?format=json");
req.onload = function() {
  if (req.status == 200) {
    SnackBar.postMessage(req.responseText);
  } else {
    SnackBar.postMessage("Error: " + req.status);
  }
}
req.send();

Этот код отправляет запрос GET в API общедоступного IP-адреса, возвращая IP-адрес устройства. Этот результат отображается в SnackBar путем вызова postMessage в SnackBar JavascriptChannel .

11. Управление файлами cookie

Ваше приложение может управлять файлами cookie в WebView с помощью класса CookieManager . На этом этапе вы отобразите список файлов cookie, очистите список файлов cookie, удалите файлы cookie и установите новые файлы cookie. Добавьте записи в _MenuOptions для каждого варианта использования файлов cookie следующим образом:

lib/src/menu.dart

enum _MenuOptions {
  navigationDelegate,
  userAgent,
  javascriptChannel,
  // Add from here ...
  listCookies,
  clearCookies,
  addCookie,
  setCookie,
  removeCookie,
  // ... to here.
}

Остальные изменения на этом этапе сосредоточены на классе Menu , включая преобразование класса Menu из режима без сохранения состояния в режим с сохранением состояния. Это изменение важно, поскольку Menu должен владеть CookieManager , а изменяемое состояние в виджетах без сохранения состояния — плохая комбинация.

Добавьте CookieManager в полученный класс State следующим образом:

lib/src/menu.dart

class Menu extends StatefulWidget {
  const Menu({required this.controller, super.key});

  final WebViewController controller;

  @override
  State<Menu> createState() => _MenuState();
}

class _MenuState extends State<Menu> {
  final cookieManager = WebViewCookieManager();       // Add this line

  @override
  Widget build(BuildContext context) {
  // ...

Класс _MenuState будет содержать код, ранее добавленный в класс Menu , а также недавно добавленный CookieManager . В следующей серии разделов вы добавите вспомогательные функции в _MenuState , которые, в свою очередь, будут вызываться еще не добавленными пунктами меню.

Получить список всех файлов cookie

Вы собираетесь использовать JavaScript, чтобы получить список всех файлов cookie. Для этого добавьте вспомогательный метод в конец класса _MenuState , называемый _onListCookies . Используя метод runJavaScriptReturningResult , ваш вспомогательный метод выполняет document.cookie в контексте JavaScript, возвращая список всех файлов cookie.

Добавьте в класс _MenuState следующее:

lib/src/menu.dart

Future<void> _onListCookies(WebViewController controller) async {
  final String cookies = await controller
      .runJavaScriptReturningResult('document.cookie') as String;
  if (!mounted) return;
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(
      content: Text(cookies.isNotEmpty ? cookies : 'There are no cookies.'),
    ),
  );
}

Очистить все файлы cookie

Чтобы удалить все файлы cookie в WebView, используйте clearCookies класса CookieManager . Метод возвращает Future<bool> , который принимает значение true если CookieManager очистил файлы cookie, и false если файлы cookie не были очищены.

Добавьте в класс _MenuState следующее:

lib/src/menu.dart

Future<void> _onClearCookies() async {
  final hadCookies = await cookieManager.clearCookies();
  String message = 'There were cookies. Now, they are gone!';
  if (!hadCookies) {
    message = 'There were no cookies to clear.';
  }
  if (!mounted) return;
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(
      content: Text(message),
    ),
  );
}

Добавить файл cookie можно, вызвав JavaScript. API, используемый для добавления Cookie в документ JavaScript , подробно описан на MDN .

Добавьте в класс _MenuState следующее:

lib/src/menu.dart

Future<void> _onAddCookie(WebViewController controller) async {
  await controller.runJavaScript('''var date = new Date();
  date.setTime(date.getTime()+(30*24*60*60*1000));
  document.cookie = "FirstName=John; expires=" + date.toGMTString();''');
  if (!mounted) return;
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(
      content: Text('Custom cookie added.'),
    ),
  );
}

Файлы cookie также можно установить с помощью CookieManager следующим образом.

Добавьте в класс _MenuState следующее:

lib/src/menu.dart

Future<void> _onSetCookie(WebViewController controller) async {
  await cookieManager.setCookie(
    const WebViewCookie(name: 'foo', value: 'bar', domain: 'flutter.dev'),
  );
  if (!mounted) return;
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(
      content: Text('Custom cookie is set.'),
    ),
  );
}

Удаление файла cookie предполагает добавление файла cookie с установленной в прошлом датой истечения срока действия.

Добавьте в класс _MenuState следующее:

lib/src/menu.dart

Future<void> _onRemoveCookie(WebViewController controller) async {
  await controller.runJavaScript(
      'document.cookie="FirstName=John; expires=Thu, 01 Jan 1970 00:00:00 UTC" ');
  if (!mounted) return;
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(
      content: Text('Custom cookie removed.'),
    ),
  );
}

Добавление пунктов меню CookieManager

Все, что осталось, — это добавить пункты меню и связать их со вспомогательными методами, которые вы только что добавили. Обновите класс _MenuState следующим образом:

lib/src/menu.dart

class _MenuState extends State<Menu> {
  final cookieManager = WebViewCookieManager();

  @override
  Widget build(BuildContext context) {
    return PopupMenuButton<_MenuOptions>(
      onSelected: (value) async {
        switch (value) {
          case _MenuOptions.navigationDelegate:
            await widget.controller
                .loadRequest(Uri.parse('https://youtube.com'));
          case _MenuOptions.userAgent:
            final userAgent = await widget.controller
                .runJavaScriptReturningResult('navigator.userAgent');
            if (!context.mounted) return;
            ScaffoldMessenger.of(context).showSnackBar(SnackBar(
              content: Text('$userAgent'),
            ));
          case _MenuOptions.javascriptChannel:
            await widget.controller.runJavaScript('''
var req = new XMLHttpRequest();
req.open('GET', "https://api.ipify.org/?format=json");
req.onload = function() {
  if (req.status == 200) {
    let response = JSON.parse(req.responseText);
    SnackBar.postMessage("IP Address: " + response.ip);
  } else {
    SnackBar.postMessage("Error: " + req.status);
  }
}
req.send();''');
          case _MenuOptions.clearCookies:                        // Add from here
            await _onClearCookies();
          case _MenuOptions.listCookies:
            await _onListCookies(widget.controller);
          case _MenuOptions.addCookie:
            await _onAddCookie(widget.controller);
          case _MenuOptions.setCookie:
            await _onSetCookie(widget.controller);
          case _MenuOptions.removeCookie:
            await _onRemoveCookie(widget.controller);            // To here.
        }
      },
      itemBuilder: (context) => [
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.navigationDelegate,
          child: Text('Navigate to YouTube'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.userAgent,
          child: Text('Show user-agent'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.javascriptChannel,
          child: Text('Lookup IP Address'),
        ),
        const PopupMenuItem<_MenuOptions>(                       // Add from here
          value: _MenuOptions.clearCookies,
          child: Text('Clear cookies'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.listCookies,
          child: Text('List cookies'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.addCookie,
          child: Text('Add cookie'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.setCookie,
          child: Text('Set cookie'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.removeCookie,
          child: Text('Remove cookie'),
        ),                                                       // To here.
      ],
    );
  }

Использование CookieManager

Чтобы использовать все функции, которые вы только что добавили в приложение, попробуйте выполнить следующие действия:

  1. Выберите «Список файлов cookie» . В нем должны быть перечислены файлы cookie Google Analytics, установленные flutter.dev.
  2. Выберите Очистить файлы cookie . Он должен сообщить, что файлы cookie действительно были удалены.
  3. Выберите «Очистить файлы cookie» еще раз. Он должен сообщить, что файлы cookie не доступны для очистки.
  4. Выберите «Список файлов cookie» . Он должен сообщить, что файлов cookie нет.
  5. Выберите Добавить файл cookie . Он должен сообщить, что файл cookie добавлен.
  6. Выберите Установить файл cookie . Он должен сообщить, что файл cookie установлен.
  7. Выберите «Список файлов cookie» , а затем в завершение выберите «Удалить файлы cookie» .

Снимок экрана эмулятора Android, на котором запущено приложение Flutter со встроенным веб-представлением, на котором показана домашняя страница Flutter.dev со списком пунктов меню, охватывающих переход на YouTube, отображение пользовательского агента и взаимодействие с банкой файлов cookie браузера.

Снимок экрана эмулятора Android, на котором запущено приложение Flutter со встроенным веб-представлением, на котором показана домашняя страница Flutter.dev со всплывающим всплывающим окном, показывающим файлы cookie, установленные в браузере.

Снимок экрана эмулятора Android, на котором запущено приложение Flutter со встроенным веб-представлением, на котором показана домашняя страница Flutter.dev со всплывающим всплывающим окном с надписью «Были файлы cookie». Теперь их больше нет!»

Снимок экрана эмулятора Android, на котором запущено приложение Flutter со встроенным веб-представлением, на котором показана домашняя страница Flutter.dev со всплывающим всплывающим сообщением с надписью «Добавлен пользовательский файл cookie».

12. Загрузите ресурсы Flutter, файлы и строки HTML в WebView.

Ваше приложение может загружать HTML-файлы различными методами и отображать их в WebView. На этом этапе вы загрузите ресурс Flutter, указанный в файле pubspec.yaml , загрузите файл, расположенный по указанному пути, и загрузите страницу, используя строку HTML.

Если вы хотите загрузить файл, расположенный по указанному пути, вам нужно будет добавить path_provider в pubspec.yaml . Это плагин Flutter для поиска часто используемых мест в файловой системе.

В командной строке выполните следующую команду:

$ flutter pub add path_provider

Для загрузки ресурса нам нужно указать путь к активу в pubspec.yaml . В pubspec.yaml добавьте следующие строки:

pubspec.yaml

# The following section is specific to Flutter packages.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true
  # Add from here
  assets:
    - assets/www/index.html
    - assets/www/styles/style.css
  # ... to here.

Чтобы добавить ресурсы в проект, выполните следующие действия:

  1. Создайте новый каталог с именем assets в корневой папке вашего проекта.
  2. Создайте новый каталог с именем www в папке assets .
  3. Создайте новый каталог с именами styles в папке www .
  4. Создайте новый файл с именем index.html в папке www .
  5. Создайте новый файл с именем style.css в папке styles .

Скопируйте и вставьте следующий код в файл index.html :

активы/www/index.html

<!DOCTYPE html>
<!-- Copyright 2013 The Flutter Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<html lang="en">
<head>
    <title>Load file or HTML string example</title>
    <link rel="stylesheet" href="styles/style.css" />
</head>
<body>

<h1>Local demo page</h1>
<p>
    This is an example page used to demonstrate how to load a local file or HTML
    string using the <a href="https://pub.dev/packages/webview_flutter">Flutter
    webview</a> plugin.
</p>

</body>
</html>

Для style.css используйте следующие несколько строк, чтобы установить стиль заголовка HTML:

активы/www/styles/style.css

h1 {
  color: blue;
}

Теперь, когда ресурсы установлены и готовы к использованию, вы можете реализовать методы, необходимые для загрузки и отображения ресурсов Flutter, файлов или строк HTML.

Загрузить ресурс Flutter

Для загрузки только что созданного вами актива все, что вам нужно сделать, это вызвать метод loadFlutterAsset с помощью WebViewController и указать в качестве параметра путь к активу. Добавьте следующий метод в конец вашего кода:

lib/src/menu.dart

Future<void> _onLoadFlutterAssetExample(
    WebViewController controller, BuildContext context) async {
  await controller.loadFlutterAsset('assets/www/index.html');
}

Загрузить локальный файл

Для загрузки файла на ваше устройство вы можете добавить метод, который будет использовать метод loadFile , опять же с помощью WebViewController , который принимает String , содержащую путь к файлу.

Сначала вам необходимо создать файл, содержащий HTML-код. Вы можете просто сделать это, добавив HTML-код в виде строки вверху вашего кода в файле menu.dart сразу под импортом.

lib/src/menu.dart

import 'dart:io';                                   // Add this line,
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';  // And this one.
import 'package:webview_flutter/webview_flutter.dart';

// Add from here ...
const String kExamplePage = '''
<!DOCTYPE html>
<html lang="en">
<head>
<title>Load file or HTML string example</title>
</head>
<body>

<h1>Local demo page</h1>
<p>
 This is an example page used to demonstrate how to load a local file or HTML
 string using the <a href="https://pub.dev/packages/webview_flutter">Flutter
 webview</a> plugin.
</p>

</body>
</html>
''';
// ... to here.

Чтобы создать File и записать в него строку HTML, вы добавите два метода. _onLoadLocalFileExample загрузит файл, указав путь в виде строки, возвращаемой методом _prepareLocalFile() . Добавьте в свой код следующие методы:

lib/src/menu.dart

Future<void> _onLoadLocalFileExample(
    WebViewController controller, BuildContext context) async {
  final String pathToIndex = await _prepareLocalFile();

  await controller.loadFile(pathToIndex);
}

static Future<String> _prepareLocalFile() async {
  final String tmpDir = (await getTemporaryDirectory()).path;
  final File indexFile = File('$tmpDir/www/index.html');

  await Directory('$tmpDir/www').create(recursive: true);
  await indexFile.writeAsString(kExamplePage);

  return indexFile.path;
}

Загрузить HTML-строку

Отобразить страницу с помощью строки HTML довольно просто. У WebViewController есть метод, который вы можете использовать под названием loadHtmlString , где вы можете указать строку HTML в качестве аргумента. Затем WebView отобразит предоставленную HTML-страницу. Добавьте в свой код следующий метод:

lib/src/menu.dart

Future<void> _onLoadFlutterAssetExample(
    WebViewController controller, BuildContext context) async {
  await controller.loadFlutterAsset('assets/www/index.html');
}

Future<void> _onLoadLocalFileExample(
    WebViewController controller, BuildContext context) async {
  final String pathToIndex = await _prepareLocalFile();

  await controller.loadFile(pathToIndex);
}

static Future<String> _prepareLocalFile() async {
  final String tmpDir = (await getTemporaryDirectory()).path;
  final File indexFile = File('$tmpDir/www/index.html');

  await Directory('$tmpDir/www').create(recursive: true);
  await indexFile.writeAsString(kExamplePage);

  return indexFile.path;
}

// Add here ...
Future<void> _onLoadHtmlStringExample(
    WebViewController controller, BuildContext context) async {
  await controller.loadHtmlString(kExamplePage);
}
// ... to here.

Добавьте пункты меню

Теперь, когда ресурсы настроены и готовы к использованию, а методы со всем функционалом созданы, меню можно обновить. Добавьте следующие записи в перечисление _MenuOptions :

lib/src/menu.dart

enum _MenuOptions {
  navigationDelegate,
  userAgent,
  javascriptChannel,
  listCookies,
  clearCookies,
  addCookie,
  setCookie,
  removeCookie,
  // Add from here ...
  loadFlutterAsset,
  loadLocalFile,
  loadHtmlString,
  // ... to here.
}

Теперь, когда перечисление обновлено, вы можете добавить параметры меню и связать их со вспомогательными методами, которые вы только что добавили. Обновите класс _MenuState следующим образом:

lib/src/menu.dart

class _MenuState extends State<Menu> {
  final cookieManager = WebViewCookieManager();

  @override
  Widget build(BuildContext context) {
    return PopupMenuButton<_MenuOptions>(
      onSelected: (value) async {
        switch (value) {
          case _MenuOptions.navigationDelegate:
            await widget.controller
                .loadRequest(Uri.parse('https://youtube.com'));
          case _MenuOptions.userAgent:
            final userAgent = await widget.controller
                .runJavaScriptReturningResult('navigator.userAgent');
            if (!context.mounted) return;
            ScaffoldMessenger.of(context).showSnackBar(SnackBar(
              content: Text('$userAgent'),
            ));
          case _MenuOptions.javascriptChannel:
            await widget.controller.runJavaScript('''
var req = new XMLHttpRequest();
req.open('GET', "https://api.ipify.org/?format=json");
req.onload = function() {
  if (req.status == 200) {
    let response = JSON.parse(req.responseText);
    SnackBar.postMessage("IP Address: " + response.ip);
  } else {
    SnackBar.postMessage("Error: " + req.status);
  }
}
req.send();''');
          case _MenuOptions.clearCookies:
            await _onClearCookies();
          case _MenuOptions.listCookies:
            await _onListCookies(widget.controller);
          case _MenuOptions.addCookie:
            await _onAddCookie(widget.controller);
          case _MenuOptions.setCookie:
            await _onSetCookie(widget.controller);
          case _MenuOptions.removeCookie:
            await _onRemoveCookie(widget.controller);
          case _MenuOptions.loadFlutterAsset:             // Add from here
            if (!mounted) return;
            await _onLoadFlutterAssetExample(widget.controller, context);
          case _MenuOptions.loadLocalFile:
            if (!mounted) return;
            await _onLoadLocalFileExample(widget.controller, context);
          case _MenuOptions.loadHtmlString:
            if (!mounted) return;
            await _onLoadHtmlStringExample(widget.controller, context);
                                                          // To here.
        }
      },
      itemBuilder: (context) => [
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.navigationDelegate,
          child: Text('Navigate to YouTube'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.userAgent,
          child: Text('Show user-agent'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.javascriptChannel,
          child: Text('Lookup IP Address'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.clearCookies,
          child: Text('Clear cookies'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.listCookies,
          child: Text('List cookies'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.addCookie,
          child: Text('Add cookie'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.setCookie,
          child: Text('Set cookie'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.removeCookie,
          child: Text('Remove cookie'),
        ),
        const PopupMenuItem<_MenuOptions>(                // Add from here
          value: _MenuOptions.loadFlutterAsset,
          child: Text('Load Flutter Asset'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.loadHtmlString,
          child: Text('Load HTML string'),
        ),
        const PopupMenuItem<_MenuOptions>(
          value: _MenuOptions.loadLocalFile,
          child: Text('Load local file'),
        ),                                                // To here.
      ],
    );
  }

Тестирование ресурсов, файла и строки HTML

Чтобы проверить, работает ли только что реализованный вами код, вы можете запустить его на своем устройстве и щелкнуть один из недавно добавленных пунктов меню. Обратите внимание, как _onLoadFlutterAssetExample использует добавленный нами style.css для изменения заголовка HTML-файла на синий цвет.

Снимок экрана эмулятора Android, на котором запущено приложение Flutter со встроенным веб-просмотром, на котором показана страница с надписью «Локальная демонстрационная страница» с заголовком синего цвета.

Снимок экрана эмулятора Android, на котором запущено приложение Flutter со встроенным веб-просмотром, на котором показана страница с надписью «Локальная демонстрационная страница» с заголовком черного цвета.

13. Все готово!

Поздравляю!!! Вы завершили кодовую лабораторию. Готовый код этой кодовой лаборатории вы можете найти в репозитории кодовой лаборатории .

Чтобы узнать больше, попробуйте другие лаборатории разработки кода Flutter .