Dodawanie komponentu WebView do aplikacji Flutter

1. Wprowadzenie

Ostatnia aktualizacja: 19.10.2021

Dzięki wtyczce WebView Flutter możesz dodać widżet WebView do aplikacji Flutter na Androida lub iOS. Na iOS widżet WebView korzysta z komponentu WKWebView, a na Androidzie – z komponentu WebView. Wtyczka może renderować widżety Flutter w widoku internetowym. W ten sposób możesz na przykład renderować menu rozwijane w widoku witryny.

Co utworzysz

W ramach tego ćwiczenia w Codelabs dowiesz się, jak za pomocą pakietu SDK Flutter stworzyć aplikację mobilną z komponentem WebView. Twoja aplikacja będzie:

  • Wyświetlaj treści z internetu w WebView
  • Wyświetlaj widżety Flutter nałożone nad elementem WebView
  • Reagowanie na zdarzenia postępu wczytywania strony
  • Sterowanie: WebView za pomocą: WebViewController
  • Blokuj strony za pomocą interfejsu NavigationDelegate
  • Obliczanie wartości wyrażeń JavaScript
  • Obsługuj wywołania zwrotne z JavaScriptu za pomocą JavascriptChannels
  • Ustawianie, usuwanie, dodawanie i wyświetlanie plików cookie
  • Ładuj i wyświetlaj kod HTML z zasobów, plików lub ciągów tekstowych zawierających kod HTML

Zrzut ekranu symulatora iPhone'a z aplikacją Flutter z osadzonym komponentem WebView przedstawiający stronę główną Flutter.dev

Zrzut ekranu przedstawiający emulator Androida z aplikacją Flutter z osadzonym komponentem WebView przedstawiający stronę główną Flutter.dev

Czego się nauczysz

Z tego ćwiczenia w Codelabs dowiesz się, jak korzystać z wtyczki webview_flutter na różne sposoby, w tym:

  • Jak skonfigurować wtyczkę webview_flutter
  • Jak nasłuchiwać zdarzeń postępu wczytywania strony
  • Jak sterować nawigacją na stronie
  • Jak nakazać urządzeniu WebView przejście do przodu i do tyłu w jego historii
  • Ocena kodu JavaScript, w tym korzystanie z zwróconych wyników
  • Jak rejestrować wywołania zwrotne w celu wywołania kodu Dart z JavaScriptu
  • Zarządzanie plikami cookie
  • Jak wczytywać i wyświetlać strony HTML z zasobów lub plików lub ciągu tekstowego zawierającego HTML

Czego potrzebujesz

2. Konfigurowanie środowiska programistycznego Flutter

Aby ukończyć ten moduł, potrzebujesz 2 oprogramowania: pakietu SDK Flutter i edytora.

Ćwiczenie z programowania możesz uruchomić na dowolnym z tych urządzeń:

  • Fizyczne urządzenie z Androidem lub iOS podłączone do komputera i ustawione w trybie programisty.
  • Symulator iOS (wymaga zainstalowania narzędzi Xcode).
  • Emulator Androida (wymaga skonfigurowania Android Studio).

3. Pierwsze kroki

Wprowadzenie do Flutter

Nowy projekt Flutter możesz utworzyć na wiele sposobów. Narzędzia do tego zadania zawierają Android Studio i Visual Studio Code. Utwórz projekt zgodnie z powiązanymi procedurami lub wykonaj poniższe polecenia w praktycznym terminalu wiersza poleceń.

$ 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.

Dodawanie wtyczki WebView Flutter jako zależność

Dodatkowe możliwości do aplikacji Flutter możesz łatwo dodać za pomocą pakietów Pub. W ramach tego ćwiczenia w Codelabs dodasz do projektu wtyczkę webview_flutter. Uruchom w terminalu te polecenia.

$ cd webview_in_flutter
$ flutter pub add webview_flutter
Resolving dependencies... 
Downloading packages... 
  leak_tracker 10.0.4 (10.0.5 available)
  leak_tracker_flutter_testing 3.0.3 (3.0.5 available)
  material_color_utilities 0.8.0 (0.11.1 available)
  meta 1.12.0 (1.14.0 available)
+ plugin_platform_interface 2.1.8
  test_api 0.7.0 (0.7.1 available)
+ webview_flutter 4.7.0
+ webview_flutter_android 3.16.0
+ webview_flutter_platform_interface 2.10.0
+ webview_flutter_wkwebview 3.13.0
Changed 5 dependencies!
5 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.

Jeśli sprawdzisz plik pubspec.yaml, zobaczysz, że zawiera on wiersz w sekcji zależności wtyczki webview_flutter.

Konfigurowanie pakietu Android minSDK

Aby używać wtyczki webview_flutter na Androidzie, ustaw minSDK na 20. Zmodyfikuj plik android/app/build.gradle w ten sposób:

android/app/build.gradle

android {
    //...

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

4. Dodawanie widżetu WebView do aplikacji Flutter

W tym kroku dodasz do aplikacji WebView. Komponenty WebView to hostowane widoki natywne, a deweloper aplikacji może zdecydować, jak chcesz hostować te widoki natywne w swojej aplikacji. Na Androidzie do wyboru masz ekrany wirtualne (obecnie domyślne w przypadku Androida) i kompozycję hybrydową. Jednak system iOS zawsze używa kompozycji hybrydowej.

Dokładniejsze omówienie różnic między wyświetlaczami wirtualnymi a kompozycją hybrydową znajdziesz w dokumentacji na temat hostowania natywnych widoków na Androida i iOS w aplikacji Flutter z widokami platformy.

Umieszczanie komponentu WebView na ekranie

Zamień zawartość pliku lib/main.dart na taką:

lib/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,
      ),
    );
  }
}

Gdy uruchomisz tę funkcję na urządzeniach z iOS lub Androidem, komponent WebView będzie wyświetlany na urządzeniu jako okno przeglądarki z pełnym spadem, co oznacza, że przeglądarka będzie wyświetlana na urządzeniu w trybie pełnoekranowym bez żadnych obramowania czy marginesów. Podczas przewijania możesz zauważyć pewne fragmenty strony, które mogą wyglądać trochę dziwnie. Dzieje się tak, ponieważ JavaScript jest obecnie wyłączony, a prawidłowe renderowanie strony flutter.dev wymaga JavaScriptu.

Uruchamianie aplikacji

Uruchom aplikację Flutter na iOS lub Androida, aby zobaczyć komponent WebView wyświetlający stronę internetową flutter.dev. Możesz też uruchomić aplikację w emulatorze Androida lub symulatorze iOS. Możesz zastąpić początkowy adres URL komponentu WebView przykładem własnej witryny.

$ flutter run

Zakładając, że masz uruchomiony odpowiedni symulator lub emulator albo podłączone urządzenie fizyczne, po skompilowaniu i wdrożeniu aplikacji na urządzeniu zobaczysz coś takiego:

Zrzut ekranu symulatora iPhone&#39;a z aplikacją Flutter z osadzonym komponentem WebView przedstawiający stronę główną Flutter.dev

Zrzut ekranu przedstawiający emulator Androida z aplikacją Flutter z osadzonym komponentem WebView przedstawiający stronę główną Flutter.dev

5. Nasłuchiwanie zdarzeń wczytania strony

Widżet WebView udostępnia kilka zdarzeń postępu wczytywania strony, których może rejestrować aplikacja. W cyklu wczytywania strony WebView wywoływane są 3 różne zdarzenia wczytywania strony: onPageStarted, onProgress i onPageFinished. W tym kroku zaimplementujesz wskaźnik wczytywania strony. Zobaczysz też, że możesz renderować treści Flutter w obszarze treści WebView.

Dodawanie zdarzeń wczytywania strony do aplikacji

Utwórz nowy plik źródłowy pod adresem lib/src/web_view_stack.dart i wypełnij go tą treścią:

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,
          ),
      ],
    );
  }
}

Ten kod otacza widżet WebView w Stack, warunkowo nakładając się na WebView elementem LinearProgressIndicator, gdy odsetek wczytywania strony jest mniejszy niż 100%. Obejmuje to stan programu, który zmienia się z czasem, dlatego ten stan został zapisany w klasie State powiązanej z zasadą StatefulWidget.

Aby korzystać z nowego widżetu WebViewStack, zmodyfikuj plik lib/main.dart w ten sposób:

lib/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(),
    );
  }
}

Po uruchomieniu aplikacji w zależności od warunków sieciowych i tego, czy przeglądarka zapisała stronę, do której przechodzisz, w pamięci podręcznej, nad obszarem treści WebView nałożony zostanie wskaźnik wczytywania strony.

6. Praca z komponentem WebViewController

Uzyskiwanie dostępu do kontrolera WebViewController z widżetu WebView

Widżet WebView umożliwia zautomatyzowaną kontrolę za pomocą interfejsu WebViewController. Ten kontroler jest udostępniany przez wywołanie zwrotne po utworzeniu widżetu WebView. Asynchroniczny charakter dostępności tego kontrolera sprawia, że jest on doskonałym kandydatem do asynchronicznej klasy Completer<T> Dart.

Zaktualizuj plik lib/src/web_view_stack.dart w ten sposób:

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,
          ),
      ],
    );
  }
}

Widżet WebViewStack używa teraz kontrolera utworzonego w otaczającym widżecie. Umożliwi to łatwe udostępnianie kontrolera urządzenia WebViewWidget innym częściom aplikacji.

Tworzenie elementów sterujących nawigacją

Działanie WebView to jedno, ale przydaje się możliwość przechodzenia do tyłu i do przodu w historii strony oraz ponownego jej wczytywania. Na szczęście dzięki WebViewController możesz dodać tę funkcję do swojej aplikacji.

Utwórz nowy plik źródłowy pod adresem lib/src/navigation_controls.dart i wypełnij go tym:

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();
          },
        ),
      ],
    );
  }
}

Ten widżet korzysta z obiektów WebViewController udostępnionych podczas tworzenia, aby umożliwić użytkownikowi kontrolowanie WebView za pomocą serii IconButton.

Dodawanie elementów nawigacyjnych do paska aplikacji

Masz teraz zaktualizowane WebViewStack i nowo utworzone NavigationControls. Teraz możesz je połączyć w zaktualizowany WebViewApp. Tutaj tworzymy wspólny WebViewController. Element WebViewApp znajduje się w górnej części drzewa widżetów, więc warto utworzyć go na tym poziomie.

Zaktualizuj plik lib/main.dart w ten sposób:

lib/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
    );
  }
}

Po uruchomieniu aplikacji powinna wyświetlić się strona internetowa z opcjami:

Zrzut ekranu symulatora iPhone&#39;a z aplikacją Flutter z osadzonym komponentem WebView przedstawiający stronę główną Flutter.dev z ustawieniami poprzedniej, następnej i następnej strony

Zrzut ekranu przedstawiający emulator Androida z aplikacją Flutter z osadzonym komponentem WebView przedstawiający stronę główną Flutter.dev z ustawieniami poprzedniej, następnej strony i ponownego wczytywania strony

7. Śledzenie nawigacji za pomocą NavigationDelegate

WebView udostępnia Twojej aplikacji uprawnienie NavigationDelegate,, które umożliwia aplikacji śledzenie i kontrolowanie nawigacji na stronie w widżecie WebView. Jeśli WebView, inicjuje nawigację, np. gdy użytkownik kliknie link, wywoływana jest funkcja NavigationDelegate. Wywołanie zwrotne NavigationDelegate pozwala określić, czy WebView ma kontynuować nawigację.

Rejestrowanie niestandardowego obiektu NavigationDelegate

Na tym etapie zarejestrujesz wywołanie zwrotne NavigationDelegate, które zablokuje nawigację na YouTube.com. Pamiętaj, że ta uproszczona implementacja blokuje też wbudowane treści z YouTube, które pojawiają się na różnych stronach dokumentacji interfejsu Flutter API.

Zaktualizuj lib/src/web_view_stack.dart w ten sposób:

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,
          ),
      ],
    );
  }
}

W następnym kroku dodasz pozycję menu, która pozwoli na testowanie elementu NavigationDelegate przy użyciu klasy WebViewController. Pozostawiamy czytelnikowi ćwiczenie w zakresie udoskonalenia logiki wywołania zwrotnego w celu zablokowania jedynie możliwości przejścia na całą stronę do YouTube.com i pozwolenia na wbudowane treści z YouTube w dokumentacji interfejsu API.

8. Dodawanie przycisku menu do paska aplikacji

W ciągu kilku następnych kroków utworzysz w widżecie AppBar przycisk menu, który będzie służyć do oceny kodu JavaScript, wywoływania kanałów JavaScriptu i zarządzania plikami cookie. Podsumowując, menu jest naprawdę przydatne.

Utwórz nowy plik źródłowy pod adresem lib/src/menu.dart i wypełnij go tym:

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'),
        ),
      ],
    );
  }
}

Gdy użytkownik wybierze opcję menu Przejdź do YouTube, zostanie wykonana metoda loadRequest stosowana przez WebViewController. Ta nawigacja zostanie zablokowana przez wywołanie zwrotne navigationDelegate utworzone w poprzednim kroku.

Aby dodać menu do ekranu urządzenia WebViewApp, zmodyfikuj lib/main.dart w ten sposób:

lib/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),
    );
  }
}

Uruchom aplikację i kliknij pozycję menu Przejdź do YouTube. Powinien wyświetlić się pasek powiadomień informujący, że kontroler nawigacyjny zablokował nawigację do YouTube.

Zrzut ekranu przedstawiający emulator Androida z aplikacją Flutter z osadzonym komponentem WebView przedstawiający stronę główną Flutter.dev z opcją menu „Przejdź do YouTube”

Zrzut ekranu przedstawiający emulator Androida z aplikacją Flutter z osadzonym komponentem WebView przedstawiający stronę główną Flutter.dev z wyświetlonym komunikatem „Blokowanie nawigacji na stronie m.youtube.com”

9. Ocena JavaScriptu

WebViewController może oceniać wyrażenia JavaScript w kontekście bieżącej strony. Istnieją 2 sposoby oceny kodu JavaScript: w przypadku kodu JavaScript, który nie zwraca wartości, użyj metody runJavaScript, a w przypadku kodu JavaScript, który zwraca wartość, użyj runJavaScriptReturningResult.

Aby włączyć JavaScript, musisz skonfigurować WebViewController z właściwością javaScriptMode ustawioną na JavascriptMode.unrestricted. Domyślnie javascriptMode ma wartość JavascriptMode.disabled.

Zaktualizuj klasę _WebViewStackState, dodając ustawienie javascriptMode w ten sposób:

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,
          ),
      ],
    );
  }
}

Gdy WebViewWidget może już wykonywać JavaScript, możesz dodać do menu opcję użycia metody runJavaScriptReturningResult.

Za pomocą edytora lub klawiatury przekonwertuj klasę Menu na StatefulWidget. Zmień lib/src/menu.dart, aby pasował do tych:

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.
      ],
    );
  }
}

Gdy klikniesz „Pokaż klienta użytkownika”, opcję menu, wynik wykonania wyrażenia JavaScript navigator.userAgent zostanie wyświetlony w Snackbar. Po uruchomieniu aplikacji możesz zauważyć, że strona Flutter.dev wygląda inaczej. Jest to wynikiem działania z włączoną obsługą JavaScriptu.

Zrzut ekranu symulatora iPhone&#39;a z aplikacją Flutter z osadzonym komponentem WebView przedstawiający stronę główną Flutter.dev z opcjami „Przejdź do YouTube” lub „Pokaż klienta użytkownika”

Zrzut ekranu symulatora iPhone&#39;a z aplikacją Flutter z osadzonym komponentem WebView przedstawiający stronę główną Flutter.dev z wyskakującym okienkiem pokazującym ciąg znaków klienta użytkownika.

10. Praca z kanałami JavaScript

Kanały JavaScript umożliwiają aplikacji rejestrowanie modułów obsługi wywołań zwrotnych w kontekście JavaScriptu WebViewWidget, które można wywoływać w celu przekazywania wartości z powrotem do kodu Dart aplikacji. W tym kroku zarejestrujesz kanał SnackBar, który będzie wywoływany w wyniku XMLHttpRequest.

Zaktualizuj klasę WebViewStack w ten sposób:

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,
          ),
      ],
    );
  }
}

Dla każdego kanału JavaScript w Set obiekt kanału jest udostępniany w kontekście JavaScriptu jako właściwość okna o nazwie takiej samej jak kanał JavaScript name. Użycie tej opcji w kontekście JavaScriptu wiąże się z wywołaniem postMessage w kanale JavaScript w celu wysłania wiadomości, która jest przekazywana do modułu obsługi wywołania zwrotnego onMessageReceived nazwanego elementu JavascriptChannel.

Aby skorzystać z dodanego powyżej kanału JavaScript, dodaj kolejny element menu, który wykonuje polecenie XMLHttpRequest w kontekście JavaScriptu i zwraca wyniki za pomocą kanału JavaScript SnackBar.

Skoro WebViewWidget zna już nasze kanały JavaScript,,możesz dodać przykład, by jeszcze bardziej rozwinąć aplikację. Aby to zrobić, dodaj do klasy Menu element PopupMenuItem i nową funkcję.

Zaktualizuj _MenuOptions o dodatkową opcję menu, dodając wartość wyliczeniową javascriptChannel, i dodaj implementację do klasy Menu w następujący sposób:

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.
      ],
    );
  }
}

Ten kod JavaScript jest wykonywany, gdy użytkownik wybierze opcję menu Przykład kanału 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();

Ten kod wysyła żądanie GET do interfejsu Public IP Address API i zwraca adres IP urządzenia. Ten wynik jest wyświetlany w polu SnackBar po wywołaniu funkcji postMessage w SnackBar JavascriptChannel.

11. Zarządzanie plikami cookie

Aplikacja może zarządzać plikami cookie w elemencie WebView za pomocą klasy CookieManager. W tym kroku wyświetli się lista plików cookie, wyczyść listę plików cookie i usuniesz pliki cookie, a także utworzysz nowe. Dodaj do _MenuOptions następujące wpisy dla poszczególnych przypadków użycia plików cookie:

lib/src/menu.dart

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

Pozostałe zmiany w tym kroku dotyczą klasy Menu, w tym przekształcenia klasy Menu z bezstanowego na stanową. Ta zmiana jest ważna, ponieważ element Menu musi należeć do elementu CookieManager, a zmienny stan widżetów bezstanowych to zła kombinacja.

Dodaj CookieManager do wynikowej klasy State w następujący sposób:

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) {
  // ...

Klasa _MenuState będzie zawierać kod dodany wcześniej w klasie Menu, a także nowo dodany element CookieManager. W kolejnej serii sekcji dodasz do funkcji _MenuState funkcje pomocnicze, które z kolei będą wywoływane przez pozycje menu, które nie zostały jeszcze dodane.

Uzyskiwanie listy wszystkich plików cookie

Użycie JavaScriptu spowoduje wyświetlenie listy wszystkich plików cookie. Aby to osiągnąć, dodaj metodę pomocniczą na końcu klasy _MenuState o nazwie _onListCookies. Metoda runJavaScriptReturningResult wykonuje polecenie document.cookie w kontekście JavaScriptu, zwracając listę wszystkich plików cookie.

Dodaj do klasy _MenuState ten kod:

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.'),
    ),
  );
}

Czyszczenie wszystkich plików cookie

Aby usunąć wszystkie pliki cookie w komponencie WebView, użyj metody clearCookies klasy CookieManager. Metoda zwraca wartość Future<bool>, która zwraca wartość true, jeśli funkcja CookieManager wyczyściła pliki cookie, lub false, jeśli nie było plików cookie do wyczyszczenia.

Dodaj do klasy _MenuState ten kod:

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),
    ),
  );
}

Plik cookie można dodać przez wywołanie JavaScriptu. Interfejs API służący do dodawania pliku cookie do dokumentu JavaScript jest dokładnie udokumentowany w MDN.

Dodaj do klasy _MenuState ten kod:

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.'),
    ),
  );
}

Pliki cookie można też tworzyć za pomocą parametru CookieManager w podany niżej sposób.

Dodaj do klasy _MenuState ten kod:

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.'),
    ),
  );
}

Usunięcie pliku cookie polega na dodaniu pliku cookie z datą ważności ustawioną w przeszłości.

Dodaj do klasy _MenuState ten kod:

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.'),
    ),
  );
}

Dodawanie pozycji menu CookieManager

Teraz musisz tylko dodać opcje menu i połączyć je z dodanymi właśnie metodami pomocniczymi. Zaktualizuj klasę _MenuState w ten sposób:

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.
      ],
    );
  }

Korzystanie z narzędzia CookieManager

Aby korzystać ze wszystkich funkcji dodanych przed chwilą do aplikacji, wykonaj te czynności:

  1. Wybierz Wyświetl listę plików cookie. Powinna ona zawierać listę plików cookie Google Analytics utworzonych przez flutter.dev.
  2. Wybierz Wyczyść pliki cookie. Powinna tam być informacja, że pliki cookie zostały usunięte.
  3. Ponownie wybierz Wyczyść pliki cookie. Powinna pojawić się informacja, że nie ma plików cookie dostępnych do usunięcia.
  4. Wybierz Wyświetl listę plików cookie. Powinna pojawić się informacja o braku plików cookie.
  5. Wybierz Dodaj plik cookie. Plik cookie powinien być raportowany jako dodany.
  6. Wybierz Ustaw plik cookie. Powinien zgłaszać plik cookie jako ustawiony.
  7. Wybierz Lista plików cookie, a na koniec kliknij Usuń plik cookie.

Zrzut ekranu przedstawiający emulator Androida z aplikacją Flutter z osadzonym komponentem WebView, który przedstawia stronę główną Flutter.dev z listą opcji menu obejmujących przejście do YouTube, klienta użytkownika i interakcję z jarkiem plików cookie przeglądarki

Zrzut ekranu przedstawiający emulator Androida z aplikacją Flutter z osadzonym komponentem WebView przedstawiający stronę główną Flutter.dev z wyskakującym okienkiem pokazującym pliki cookie ustawione w przeglądarce

Zrzut ekranu przedstawiający emulator Androida z aplikacją Flutter z osadzonym komponentem WebView przedstawiający stronę główną Flutter.dev z wyskakującym okienkiem informującym, że były pliki cookie Teraz poszły!

Zrzut ekranu przedstawiający emulator Androida z aplikacją Flutter z osadzonym komponentem WebView przedstawiający stronę główną Flutter.dev z wyskakującym okienkiem informującym o dodaniu niestandardowego pliku cookie.

12. Wczytywanie zasobów, plików i ciągów HTML z Flutter w komponencie WebView

Aplikacja może wczytywać pliki HTML na różne sposoby i wyświetlać je w komponencie WebView. W tym kroku wczytasz zasób Flutter określony w pliku pubspec.yaml, wczytasz plik znajdujący się w podanej ścieżce i wczytasz stronę za pomocą ciągu HTML.

Jeśli chcesz wczytać plik znajdujący się pod określoną ścieżką, path_provider do pubspec.yaml. To wtyczka Flutter służąca do znajdowania często używanych lokalizacji w systemie plików.

W wierszu poleceń uruchom następujące polecenie:

$ flutter pub add path_provider

Aby wczytać zasób, musimy określić ścieżkę do niego w tabeli pubspec.yaml. W pliku pubspec.yaml dodaj te wiersze:

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.

Aby dodać zasoby do projektu:

  1. W folderze głównym projektu utwórz nowy katalog o nazwie assets.
  2. W folderze assets utwórz nowy Katalog o nazwie www.
  3. W folderze www utwórz nowy Katalog o nazwie styles.
  4. Utwórz nowy Plik o nazwie index.html w folderze www.
  5. Utwórz nowy Plik o nazwie style.css w folderze styles.

Skopiuj ten kod i wklej go w pliku index.html:

assets/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>

W przypadku pliku style.css użyj kilku poniższych wierszy, aby ustawić styl nagłówka HTML:

assets/www/styles/style.css

h1 {
  color: blue;
}

Gdy zasoby są gotowe i są gotowe do użycia, możesz wdrożyć metody niezbędne do wczytywania i wyświetlania zasobów, plików i ciągów HTML w technologii Flutter.

Wczytaj zasób Flutter

Aby wczytać utworzony przed chwilą zasób, musisz tylko wywołać metodę loadFlutterAsset za pomocą metody WebViewController i podać jako parametr ścieżkę do zasobu. Na końcu kodu dodaj następującą metodę:

lib/src/menu.dart

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

Wczytaj plik lokalny

Aby wczytać plik na urządzeniu, możesz dodać metodę, która będzie korzystała z metody loadFile, ponownie używając metody WebViewController, która przyjmuje String ze ścieżką do pliku.

Najpierw musisz utworzyć plik zawierający kod HTML. Wystarczy, że dodasz kod HTML w postaci ciągu na początku kodu w pliku menu.dart tuż pod elementami importu.

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.

Aby utworzyć obiekt File i zapisać w pliku ciąg HTML ciąg, musisz dodać 2 metody. _onLoadLocalFileExample wczyta plik, podając ścieżkę w postaci ciągu znaków zwracanego przez metodę _prepareLocalFile(). Dodaj do kodu te metody:

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;
}

Wczytaj ciąg HTML

Wyświetlanie strony przez podanie ciągu HTML jest całkiem proste. WebViewController zawiera metodę o nazwie loadHtmlString, w której możesz podać ciąg HTML jako argument. W elemencie WebView zostanie wyświetlona podana strona HTML. Dodaj do kodu następującą metodę:

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.

Dodaj pozycje menu

Gdy zasoby są już skonfigurowane i gotowe do użycia, a metody ze wszystkimi funkcjami są gotowe, więc możesz zaktualizować menu. Dodaj do wyliczenia _MenuOptions te wpisy:

lib/src/menu.dart

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

Po zaktualizowaniu wyliczenia możesz dodać opcje menu i połączyć je z dodanymi przed chwilą metodami pomocniczymi. Zaktualizuj klasę _MenuState w ten sposób:

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.
      ],
    );
  }

Testowanie zasobów, pliku i ciągu HTML

Aby sprawdzić, czy kod zadziałał przed chwilą, możesz uruchomić go na urządzeniu i kliknąć jedną z nowo dodanych pozycji menu. Zwróć uwagę, że _onLoadFlutterAssetExample używa dodanego przez nas elementu style.css, aby zmienić kolor nagłówka pliku HTML na niebieski.

Zrzut ekranu przedstawiający emulator Androida z aplikacją Flutter z osadzonym komponentem WebView, na którym wyświetla się strona o nazwie „Lokalna strona demonstracyjna” z niebieskim tytułem

Zrzut ekranu przedstawiający emulator Androida z aplikacją Flutter z osadzonym komponentem WebView, na którym wyświetla się strona o nazwie „Lokalna strona demonstracyjna” z czarnym tytułem

13. Wszystko gotowe

Gratulacje! Ćwiczenie z programowania zostało ukończone. Ukończony kod tego ćwiczenia z programowania znajdziesz w repozytorium ćwiczeń z programowania.

Aby dowiedzieć się więcej, wykonaj inne ćwiczenia z programowania Flutter.