WebView zur Flutter-App hinzufügen

1. Einführung

Zuletzt aktualisiert: 19.10.2021

Mit dem WebView Flutter-Plug-in können Sie Ihrer Android- oder iOS-Flutter-App ein WebView-Widget hinzufügen. Unter iOS wird das WebView-Widget von einem WKWebView unterstützt, unter Android von einem WebView. Das Plug-in kann Flutter-Widgets über der Webansicht rendern. So ist es beispielsweise möglich, ein Drop-down-Menü über der Webansicht zu rendern.

Aufgaben

In diesem Codelab erstellen Sie Schritt für Schritt eine mobile App mit einer WebView mithilfe des Flutter SDK. Mit der Anwendung können Sie Folgendes tun:

  • Webinhalte in einem WebView anzeigen
  • Flutter-Widgets über dem WebView anzeigen
  • Auf Ereignisse zum Fortschritt des Seitenaufbaus reagieren
  • WebView über die WebViewController steuern
  • Websites mit NavigationDelegate blockieren
  • JavaScript-Ausdrücke auswerten
  • Callbacks aus JavaScript mit JavascriptChannels verarbeiten
  • Cookies setzen, entfernen, hinzufügen oder anzeigen
  • HTML aus Assets, Dateien oder Strings mit HTML laden und anzeigen

iPhone-Simulator mit einer Flutter-App mit eingebetteter WebView, in der die Flutter.dev-Startseite angezeigt wird

Android-Emulator, auf dem eine Flutter-App mit einer eingebetteten Webansicht ausgeführt wird, in der die Flutter.dev-Startseite angezeigt wird

Lerninhalte

In diesem Codelab erfahren Sie, wie Sie das webview_flutter-Plug-in auf verschiedene Arten verwenden können, z. B.:

  • webview_flutter-Plug-in konfigurieren
  • Ereignisse für den Fortschritt des Seitenaufbaus erfassen
  • Seitennavigation steuern
  • WebView anweisen, in der Historie vor- und zurückzugehen
  • JavaScript auswerten, einschließlich der Verwendung zurückgegebener Ergebnisse
  • Callbacks registrieren, um Dart-Code aus JavaScript aufzurufen
  • Cookies verwalten
  • HTML-Seiten aus Assets, Dateien oder einem String mit HTML laden und anzeigen

Voraussetzungen

2. Flutter-Entwicklungsumgebung einrichten

Für dieses Lab benötigen Sie zwei Softwarekomponenten: das Flutter SDK und einen Editor.

Sie können das Codelab auf einem der folgenden Geräte ausführen:

  • Ein physisches Android- oder iOS-Gerät, das mit Ihrem Computer verbunden ist und auf den Entwicklermodus eingestellt ist.
  • Der iOS-Simulator (erfordert die Installation von Xcode-Tools).
  • Android Emulator (muss in Android Studio eingerichtet werden)

3. Erste Schritte

Erste Schritte mit Flutter

Es gibt verschiedene Möglichkeiten, ein neues Flutter-Projekt zu erstellen. Sowohl Android Studio als auch Visual Studio Code bieten Tools für diese Aufgabe. Folgen Sie entweder der verlinkten Anleitung, um ein Projekt zu erstellen, oder führen Sie die folgenden Befehle in einem geeigneten Befehlszeilenterminal aus.

$ 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-Plug-in als Abhängigkeit hinzufügen

Am besten fügen Sie einer Flutter-App zusätzliche Funktionen hinzu, indem Sie Pub-Pakete verwenden. In diesem Codelab fügen Sie Ihrem Projekt das webview_flutter-Plug-in hinzu. Führen Sie im Terminal die folgenden Befehle aus.

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

Wenn Sie die Datei pubspec.yaml untersuchen, sehen Sie, dass sie jetzt eine Zeile im Abschnitt „dependencies“ für das webview_flutter-Plug-in enthält.

Android-minSDK konfigurieren

Wenn Sie das webview_flutter-Plug-in unter Android verwenden möchten, müssen Sie minSDK auf 20 festlegen. Ändern Sie die Datei android/app/build.gradle so:

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. WebView-Widget zur Flutter-App hinzufügen

In diesem Schritt fügen Sie Ihrer Anwendung ein WebView hinzu. WebViews sind gehostete integrierte Ansichten. Als App-Entwickler haben Sie die Möglichkeit, diese integrierten Ansichten in Ihrer App zu hosten. Unter Android können Sie zwischen virtuellen Displays (Standard für Android) und der Hybrid-Komposition wählen. Unter iOS wird jedoch immer die Hybrid-Komposition verwendet.

Eine ausführliche Erläuterung der Unterschiede zwischen virtuellen Displays und der Hybrid-Zusammensetzung finden Sie in der Dokumentation unter Hosting native Android and iOS views in your Flutter app with Platform Views .

Webview auf dem Bildschirm anzeigen

Ersetzen Sie den Inhalt von lib/main.dart durch Folgendes:

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

Wenn Sie diesen Code auf iOS oder Android ausführen, wird eine WebView als randloses Browserfenster auf Ihrem Gerät angezeigt. Das bedeutet, dass der Browser auf Ihrem Gerät im Vollbildmodus ohne Rahmen oder Rand angezeigt wird. Beim Scrollen werden Sie feststellen, dass einige Teile der Seite etwas seltsam aussehen. Das liegt daran, dass JavaScript deaktiviert ist. Für das korrekte Rendern von flutter.dev ist JavaScript erforderlich.

App ausführen

Führen Sie die Flutter-App unter iOS oder Android aus, um eine Webansicht zu sehen, in der die Website flutter.dev angezeigt wird. Alternativ können Sie die App in einem Android-Emulator oder einem iOS-Simulator ausführen. Sie können die ursprüngliche WebView-URL durch beispielsweise Ihre eigene Website ersetzen.

$ flutter run

Wenn Sie den entsprechenden Simulator oder Emulator ausführen oder ein physisches Gerät angeschlossen haben, sollte nach dem Kompilieren und Bereitstellen der App auf Ihrem Gerät Folgendes angezeigt werden:

iPhone-Simulator mit einer Flutter-App mit eingebetteter WebView, in der die Flutter.dev-Startseite angezeigt wird

Android-Emulator, auf dem eine Flutter-App mit einer eingebetteten Webansicht ausgeführt wird, in der die Flutter.dev-Startseite angezeigt wird

5. Auf Seitenaufbau-Ereignisse warten

Das WebView-Widget bietet mehrere Ereignisse für den Fortschritt des Seitenaufbaus, auf die Ihre App reagieren kann. Während des WebView-Seitenladezyklus werden drei verschiedene Seitenladeereignisse ausgelöst: onPageStarted, onProgress und onPageFinished. In diesem Schritt implementieren Sie eine Anzeige für das Laden der Seite. Außerdem wird dadurch gezeigt, dass Sie Flutter-Inhalte über dem WebView-Inhaltsbereich rendern können.

Seitenaufbau-Ereignisse in Ihre App einfügen

Erstellen Sie eine neue Quelldatei unter lib/src/web_view_stack.dart und fügen Sie ihr folgenden Inhalt hinzu:

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

In diesem Code wird das WebView-Widget in ein Stack eingebettet und das WebView wird bedingt mit einem LinearProgressIndicator überlagert, wenn der Prozentsatz des Seitenaufbaus weniger als 100 % beträgt. Da sich der Programmstatus im Laufe der Zeit ändert, haben Sie ihn in einer State-Klasse gespeichert, die mit einem StatefulWidget verknüpft ist.

Wenn Sie dieses neue WebViewStack-Widget verwenden möchten, müssen Sie Ihre lib/main.dart so ändern:

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

Wenn Sie die App ausführen, wird je nach Netzwerkbedingungen und je nachdem, ob der Browser die Seite, zu der Sie navigieren, im Cache gespeichert hat, eine Ladeanzeige für die Seite über dem Inhaltsbereich WebView eingeblendet.

6. Mit dem WebViewController arbeiten

Über das WebView-Widget auf den WebViewController zugreifen

Das WebView-Widget ermöglicht die programmatische Steuerung mit einer WebViewController. Dieser Controller wird nach der Erstellung des WebView-Widgets über einen Callback zur Verfügung gestellt. Aufgrund der asynchronen Verfügbarkeit dieses Controllers ist er ein hervorragender Kandidat für die asynchrone Completer<T>-Klasse von Dart.

Aktualisieren Sie lib/src/web_view_stack.dart so:

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

Das WebViewStack-Widget verwendet jetzt einen Controller, der im umgebenden Widget erstellt wurde. Dadurch kann der Controller für WebViewWidget mit anderen Teilen der App geteilt werden.

Navigationssteuerelemente erstellen

Ein funktionierendes WebView ist eine Sache, aber die Möglichkeit, vorwärts und rückwärts durch den Seitenverlauf zu navigieren und die Seite neu zu laden, wäre eine nützliche Ergänzung. Mit einem WebViewController können Sie diese Funktion in Ihre App einfügen.

Erstellen Sie eine neue Quelldatei unter lib/src/navigation_controls.dart und fügen Sie Folgendes ein:

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

Dieses Widget verwendet das WebViewController, das ihm bei der Erstellung zugewiesen wurde, damit der Nutzer das WebView über eine Reihe von IconButtons steuern kann.

Navigationssteuerelemente zur App-Leiste hinzufügen

Mit der aktualisierten WebViewStack und der neu erstellten NavigationControls ist es jetzt an der Zeit, alles in einer aktualisierten WebViewApp zusammenzufügen. Hier erstellen wir das freigegebene WebViewController. Da sich WebViewApp in dieser App oben im Widget-Baum befindet, ist es sinnvoll, es auf dieser Ebene zu erstellen.

Aktualisieren Sie die Datei lib/main.dart so:

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

Beim Ausführen der App sollte eine Webseite mit Steuerelementen angezeigt werden:

iPhone-Simulator mit einer Flutter-App mit eingebetteter WebView, in der die Flutter.dev-Startseite mit Steuerelementen für die vorherige Seite, die nächste Seite und das Aktualisieren der Seite angezeigt wird

Android-Emulator mit einer Flutter-App mit eingebetteter WebView, in der die Flutter.dev-Startseite mit Steuerelementen für die vorherige Seite, die nächste Seite und das Aktualisieren der Seite angezeigt wird

7. Navigation mit NavigationDelegate im Blick behalten

WebView stellt Ihrer App einen NavigationDelegate, zur Verfügung, mit dem Ihre App die Seitennavigation des WebView-Widgets verfolgen und steuern kann. Wenn eine Navigation von WebView, initiiert wird, z. B. wenn ein Nutzer auf einen Link klickt, wird NavigationDelegate aufgerufen. Mit dem NavigationDelegate-Callback kann gesteuert werden, ob WebView mit der Navigation fortfährt.

Benutzerdefinierten NavigationDelegate registrieren

In diesem Schritt registrieren Sie einen NavigationDelegate-Callback, um die Navigation zu YouTube.com zu blockieren. Beachten Sie, dass diese einfache Implementierung auch Inline-YouTube-Inhalte blockiert, die auf verschiedenen Flutter-API-Dokumentationsseiten angezeigt werden.

Aktualisieren Sie lib/src/web_view_stack.dart so:

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

Im nächsten Schritt fügen Sie einen Menüpunkt hinzu, mit dem Sie Ihr NavigationDelegate mithilfe der Klasse WebViewController testen können. Es bleibt dem Leser überlassen, die Logik des Rückrufs so zu erweitern, dass die Navigation auf YouTube.com nur für die gesamte Seite blockiert wird und Inline-YouTube-Inhalte in der API-Dokumentation weiterhin zulässig sind.

8. Menüschaltfläche zur App-Leiste hinzufügen

In den nächsten Schritten erstellen Sie eine Menüschaltfläche im AppBar-Widget, mit der JavaScript ausgewertet, JavaScript-Channels aufgerufen und Cookies verwaltet werden. Insgesamt ein wirklich nützliches Menü.

Erstellen Sie eine neue Quelldatei unter lib/src/menu.dart und fügen Sie Folgendes ein:

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

Wenn der Nutzer die Menüoption Zu YouTube wechseln auswählt, wird die Methode loadRequest des WebViewController ausgeführt. Diese Navigation wird durch den navigationDelegate-Callback blockiert, den Sie im vorherigen Schritt erstellt haben.

Wenn Sie das Menü dem Bildschirm von WebViewApp hinzufügen möchten, ändern Sie lib/main.dart so:

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

Führen Sie Ihre App aus und tippen Sie auf den Menüpunkt Zu YouTube wechseln. Es sollte eine Snackbar angezeigt werden, in der Sie darüber informiert werden, dass der Navigationscontroller die Navigation zu YouTube blockiert hat.

Android-Emulator mit einer Flutter-App, in die eine Webview eingebettet ist, in der die Flutter.dev-Startseite mit einem Menüpunkt angezeigt wird, über den man zu YouTube wechseln kann.

Ein Android-Emulator, auf dem eine Flutter-App mit einer eingebetteten Webview ausgeführt wird, in der die Flutter.dev-Startseite angezeigt wird. Ein Pop-up-Toast mit dem Text „Navigation zu m.youtube.com wird blockiert“ wird angezeigt.

9. JavaScript auswerten

Mit WebViewController können JavaScript-Ausdrücke im Kontext der aktuellen Seite ausgewertet werden. Es gibt zwei verschiedene Möglichkeiten, JavaScript auszuwerten: Für JavaScript-Code, der keinen Wert zurückgibt, verwenden Sie runJavaScript. Für JavaScript-Code, der einen Wert zurückgibt, verwenden Sie runJavaScriptReturningResult.

Damit JavaScript aktiviert wird, müssen Sie WebViewController konfigurieren und das Attribut javaScriptMode auf JavascriptMode.unrestricted festlegen. Standardmäßig ist javascriptMode auf JavascriptMode.disabled festgelegt.

Aktualisieren Sie die Klasse _WebViewStackState, indem Sie die Einstellung javascriptMode wie folgt hinzufügen:

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

Da die WebViewWidget jetzt JavaScript ausführen kann, können Sie dem Menü eine Option hinzufügen, um die runJavaScriptReturningResult-Methode zu verwenden.

Konvertieren Sie die Klasse „Menu“ entweder mit dem Editor oder über die Tastatur in ein StatefulWidget. Ändern Sie lib/src/menu.dart so:

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

Wenn Sie auf die Menüoption „User-Agent anzeigen“ tippen, wird das Ergebnis der Ausführung des JavaScript-Ausdrucks navigator.userAgent in einem Snackbar angezeigt. Wenn Sie die App ausführen, werden Sie feststellen, dass die Seite „Flutter.dev“ anders aussieht. Das ist das Ergebnis der Ausführung mit aktiviertem JavaScript.

iPhone-Simulator mit einer Flutter-App mit eingebetteter WebView, in der die Flutter.dev-Startseite mit Menüelementen angezeigt wird, die die Optionen „Zu YouTube wechseln“ oder „User-Agent anzeigen“ enthalten.

Ein iPhone-Simulator, auf dem eine Flutter-App mit einer eingebetteten Webview ausgeführt wird, in der die Flutter.dev-Startseite angezeigt wird. Ein Toast-Pop-up-Fenster zeigt den User-Agent-String an.

10. Mit JavaScript-Channels arbeiten

Mit JavaScript-Channels kann Ihre App Callback-Handler im JavaScript-Kontext des WebViewWidget registrieren, die aufgerufen werden können, um Werte an den Dart-Code der App zurückzugeben. In diesem Schritt registrieren Sie einen SnackBar-Channel, der mit dem Ergebnis eines XMLHttpRequest aufgerufen wird.

Aktualisieren Sie die Klasse WebViewStack so:

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

Für jeden JavaScript-Channel in Set wird im JavaScript-Kontext ein Channel-Objekt als Fensterattribut mit demselben Namen wie der JavaScript-Channel name verfügbar gemacht. Wenn Sie dies aus dem JavaScript-Kontext verwenden, müssen Sie postMessage für den JavaScript-Channel aufrufen, um eine Nachricht zu senden, die an den onMessageReceived-Callback-Handler des benannten JavascriptChannel übergeben wird.

Wenn Sie den zuvor hinzugefügten JavaScript-Channel verwenden möchten, fügen Sie ein weiteres Menüelement hinzu, das ein XMLHttpRequest im JavaScript-Kontext ausführt und die Ergebnisse über den SnackBar-JavaScript-Channel zurückgibt.

Da WebViewWidget jetzt über unsere JavaScript-Channels informiert ist,, fügen Sie ein Beispiel hinzu, um die App weiter zu entwickeln. Fügen Sie dazu der Menu-Klasse ein zusätzliches PopupMenuItem hinzu und fügen Sie die zusätzliche Funktion hinzu.

Aktualisieren Sie _MenuOptions mit der zusätzlichen Menüoption, indem Sie den Enumerationswert javascriptChannel hinzufügen, und fügen Sie der Klasse Menu eine Implementierung hinzu:

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

Dieses JavaScript wird ausgeführt, wenn der Nutzer die Menüoption JavaScript Channel Example auswählt.

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();

Dieser Code sendet eine GET-Anfrage an eine API für öffentliche IP-Adressen und gibt die IP-Adresse des Geräts zurück. Dieses Ergebnis wird in einem SnackBar angezeigt, indem postMessage für das SnackBar JavascriptChannel aufgerufen wird.

11. Cookies verwalten

Ihre App kann Cookies im WebView mit der Klasse CookieManager verwalten. In diesem Schritt zeigen Sie eine Liste von Cookies an, löschen die Liste von Cookies, löschen Cookies und legen neue Cookies fest. Fügen Sie für jeden der Cookie-Anwendungsfälle Einträge in die _MenuOptions ein:

lib/src/menu.dart

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

Die restlichen Änderungen in diesem Schritt beziehen sich auf die Klasse Menu, einschließlich der Umwandlung der Klasse Menu von zustandslos zu zustandsbehaftet. Diese Änderung ist wichtig, da das Menu das CookieManager besitzen muss und ein veränderlicher Status in zustandslosen Widgets eine schlechte Kombination ist.

Fügen Sie der resultierenden State-Klasse den CookieManager so hinzu:

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

Die Klasse _MenuState enthält den Code, der zuvor in der Klasse Menu hinzugefügt wurde, sowie die neu hinzugefügte CookieManager. In den nächsten Abschnitten fügen Sie _MenuState Hilfsfunktionen hinzu, die wiederum von den noch hinzuzufügenden Menüpunkten aufgerufen werden.

Liste aller Cookies abrufen

Sie verwenden JavaScript, um eine Liste aller Cookies abzurufen. Fügen Sie dazu am Ende der Klasse _MenuState eine Hilfsmethode mit dem Namen _onListCookies hinzu. Mit der Methode runJavaScriptReturningResult wird document.cookie im JavaScript-Kontext ausgeführt und eine Liste aller Cookies zurückgegeben.

Fügen Sie der Klasse _MenuState Folgendes hinzu:

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

Alle Cookies löschen

Wenn Sie alle Cookies in der WebView löschen möchten, verwenden Sie die Methode clearCookies der Klasse CookieManager. Die Methode gibt ein Future<bool> zurück, das in true aufgelöst wird, wenn der CookieManager die Cookies gelöscht hat, und in false, wenn keine Cookies zum Löschen vorhanden waren.

Fügen Sie der Klasse _MenuState Folgendes hinzu:

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

Cookies können durch Aufrufen von JavaScript hinzugefügt werden. Die API zum Hinzufügen eines Cookies zu einem JavaScript-Dokument ist auf MDN ausführlich dokumentiert.

Fügen Sie der Klasse _MenuState Folgendes hinzu:

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

Cookies können auch mit CookieManager festgelegt werden.

Fügen Sie der Klasse _MenuState Folgendes hinzu:

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

Wenn Sie ein Cookie entfernen möchten, müssen Sie ein Cookie mit einem Ablaufdatum in der Vergangenheit hinzufügen.

Fügen Sie der Klasse _MenuState Folgendes hinzu:

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-Menüelemente hinzufügen

Jetzt müssen Sie nur noch die Menüoptionen hinzufügen und sie mit den gerade hinzugefügten Hilfsmethoden verknüpfen. Aktualisieren Sie die Klasse _MenuState so:

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 verwenden

So können Sie alle Funktionen nutzen, die Sie der App gerade hinzugefügt haben:

  1. Wählen Sie Cookies auflisten aus. Dort sollten die von flutter.dev gesetzten Google Analytics-Cookies aufgeführt sein.
  2. Wählen Sie Cookies löschen aus. Es sollte angezeigt werden, dass die Cookies tatsächlich gelöscht wurden.
  3. Wählen Sie noch einmal Cookies löschen aus. Es sollte gemeldet werden, dass keine Cookies zum Löschen verfügbar waren.
  4. Wählen Sie Cookies auflisten aus. Es sollte gemeldet werden, dass keine Cookies vorhanden sind.
  5. Wählen Sie Cookie hinzufügen aus. Das Cookie sollte als hinzugefügt gemeldet werden.
  6. Wählen Sie Cookie festlegen aus. Das Cookie sollte als festgelegt gemeldet werden.
  7. Wählen Sie Cookies auflisten und dann Cookie entfernen aus.

Android-Emulator mit einer Flutter-App mit eingebetteter WebView, in der die Flutter.dev-Startseite mit einer Liste von Menüoptionen angezeigt wird, die das Navigieren zu YouTube, die Anzeige des User-Agents und die Interaktion mit dem Cookie-Speicher des Browsers abdecken

Android-Emulator, auf dem eine Flutter-App mit einer eingebetteten WebView ausgeführt wird, in der die Flutter.dev-Startseite angezeigt wird. Außerdem ist ein Toast-Dialogfeld mit den im Browser festgelegten Cookies zu sehen.

Ein Android-Emulator, auf dem eine Flutter-App mit einer eingebetteten Webview ausgeführt wird, in der die Flutter.dev-Startseite mit einem Toast-Dialogfeld mit dem Text „There were cookies.“ (Es gab Cookies.) angezeigt wird. Jetzt sind sie weg!

Ein Android-Emulator, auf dem eine Flutter-App mit einer eingebetteten WebView ausgeführt wird, in der die Flutter.dev-Startseite angezeigt wird. Außerdem ist ein Toast-Dialogfeld mit dem Text „Benutzerdefiniertes Cookie hinzugefügt“ zu sehen.

12. Flutter-Assets, -Dateien und -HTML-Strings in der WebView laden

Ihre App kann HTML-Dateien mit verschiedenen Methoden laden und in der WebView anzeigen. In diesem Schritt laden Sie ein in der Datei pubspec.yaml angegebenes Flutter-Asset, eine Datei am angegebenen Pfad und eine Seite mit einem HTML-String.

Wenn Sie eine Datei laden möchten, die sich an einem bestimmten Pfad befindet, müssen Sie der pubspec.yaml die path_provider hinzufügen. Dies ist ein Flutter-Plug-in zum Auffinden häufig verwendeter Speicherorte im Dateisystem.

Führen Sie in der Befehlszeile folgenden Befehl aus:

$ flutter pub add path_provider

Zum Laden des Assets muss der Pfad zum Asset in pubspec.yaml angegeben werden. Fügen Sie in pubspec.yaml die folgenden Zeilen hinzu:

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.

So fügen Sie die Assets Ihrem Projekt hinzu:

  1. Erstellen Sie im Stammordner Ihres Projekts ein neues Verzeichnis mit dem Namen assets.
  2. Erstellen Sie im Ordner assets ein neues Verzeichnis mit dem Namen www.
  3. Erstellen Sie im Ordner www ein neues Verzeichnis mit dem Namen styles.
  4. Erstellen Sie im Ordner www eine neue Datei mit dem Namen index.html.
  5. Erstellen Sie im Ordner styles eine neue Datei mit dem Namen style.css.

Kopieren Sie den folgenden Code in die Datei 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>

Verwenden Sie für style.css die folgenden Zeilen, um den HTML-Headerstil festzulegen:

assets/www/styles/style.css

h1 {
  color: blue;
}

Nachdem die Assets festgelegt und einsatzbereit sind, können Sie die Methoden implementieren, die zum Laden und Anzeigen von Flutter-Assets, ‑Dateien oder HTML-Strings erforderlich sind.

Flutter-Asset laden

Um das gerade erstellte Asset zu laden, müssen Sie nur die Methode loadFlutterAsset mit dem WebViewController aufrufen und den Pfad zum Asset als Parameter angeben. Fügen Sie am Ende Ihres Codes die folgende Methode hinzu:

lib/src/menu.dart

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

Lokale Datei laden

Wenn Sie eine Datei auf Ihr Gerät laden möchten, können Sie eine Methode hinzufügen, die die Methode loadFile verwendet. Dazu nutzen Sie wieder WebViewController, das ein String mit dem Pfad zur Datei akzeptiert.

Sie müssen zuerst eine Datei mit dem HTML-Code erstellen. Fügen Sie dazu den HTML-Code als String oben in den Code in der Datei menu.dart direkt unter den Importen ein.

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.

Um eine File zu erstellen und den HTML-String in die Datei zu schreiben, fügen Sie zwei Methoden hinzu. Mit _onLoadLocalFileExample wird die Datei geladen, indem der Pfad als String angegeben wird, der von der Methode _prepareLocalFile() zurückgegeben wird. Fügen Sie Ihrem Code die folgenden Methoden hinzu:

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-String laden

Eine Seite über einen HTML-String anzuzeigen, ist ganz einfach. Die WebViewController hat eine Methode namens loadHtmlString, die Sie verwenden können. Dort können Sie den HTML-String als Argument angeben. Im WebView wird dann die bereitgestellte HTML-Seite angezeigt. Fügen Sie Ihrem Code die folgende Methode hinzu:

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.

Speisen und Getränke auf der Speisekarte hinzufügen

Nachdem die Assets festgelegt und einsatzbereit sind und die Methoden mit allen Funktionen erstellt wurden, kann das Menü aktualisiert werden. Fügen Sie dem _MenuOptions-Enum die folgenden Einträge hinzu:

lib/src/menu.dart

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

Nachdem das Enum aktualisiert wurde, können Sie die Menüoptionen hinzufügen und sie mit den gerade hinzugefügten Hilfsmethoden verknüpfen. Aktualisieren Sie die Klasse _MenuState so:

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

Assets, Datei und HTML-String testen

Wenn Sie testen möchten, ob der gerade implementierte Code funktioniert, können Sie ihn auf Ihrem Gerät ausführen und auf eines der neu hinzugefügten Menüelemente klicken. Beachten Sie, wie mit dem _onLoadFlutterAssetExample das von uns hinzugefügte style.css verwendet wird, um die Überschrift der HTML-Datei in Blau zu ändern.

Android-Emulator mit einer Flutter-App mit eingebetteter WebView, in der eine Seite mit der Bezeichnung „Lokale Demoseite“ mit blauem Titel angezeigt wird

Ein Android-Emulator, auf dem eine Flutter-App mit einer eingebetteten Webview ausgeführt wird, in der eine Seite mit der Bezeichnung „Local demo page“ (Lokale Demoseite) mit dem Titel in Schwarz angezeigt wird

13. Fertig!

Herzlichen Glückwunsch!!! Sie haben das Codelab abgeschlossen. Den vollständigen Code für dieses Codelab finden Sie im Codelab-Repository.

Weitere Informationen finden Sie in den anderen Flutter-Codelabs.