Menambahkan WebView ke aplikasi Flutter

1. Pengantar

Terakhir Diperbarui: 19-10-2021

Dengan plugin WebView Flutter, Anda dapat menambahkan widget WebView ke aplikasi Flutter Android atau iOS Anda. Di iOS, widget WebView didukung oleh WKWebView, sedangkan di Android, widget WebView didukung oleh WebView. Plugin tersebut dapat merender widget Flutter melalui tampilan web. Jadi misalnya, menu drop-down dapat dirender melalui tampilan web.

Yang akan Anda build

Dalam codelab ini, Anda akan mem-build aplikasi seluler langkah demi langkah dengan WebView menggunakan Flutter SDK. Aplikasi Anda akan:

  • Menampilkan konten web di WebView
  • Menampilkan widget Flutter yang ditumpuk melalui WebView
  • Menanggapi peristiwa progres pemuatan halaman
  • Mengontrol WebView melalui WebViewController
  • Memblokir berbagai situs menggunakan NavigationDelegate
  • Mengevaluasi ekspresi JavaScript
  • Menangani callback dari JavaScript dengan JavascriptChannels
  • Menetapkan, menghapus, menambahkan, atau menampilkan cookie
  • Memuat dan menampilkan HTML dari aset, file, atau String yang berisi HTML

Yang akan Anda pelajari

Dalam codelab ini, Anda akan mempelajari cara menggunakan plugin webview_flutter dalam berbagai cara, termasuk:

  • Cara mengonfigurasi plugin webview_flutter
  • Cara memproses peristiwa progres pemuatan halaman
  • Cara mengontrol navigasi halaman
  • Cara memerintahkan WebView untuk mundur dan maju melalui historinya
  • Cara mengevaluasi JavaScript, termasuk cara menggunakan hasil yang ditampilkan
  • Cara mendaftarkan callback untuk memanggil kode Dart dari JavaScript
  • Cara mengelola cookie
  • Cara memuat dan menampilkan halaman HTML dari aset atau file atau String yang berisi HTML

Yang akan Anda butuhkan

2. Menyiapkan lingkungan Flutter Anda

Anda memerlukan dua software untuk menyelesaikan lab ini: Flutter SDK dan editor.

Anda dapat menjalankan codelab ini menggunakan perangkat berikut:

  • Perangkat fisik seluler (Android atau iOS) yang terhubung ke komputer Anda dan disetel ke mode developer.
  • Simulator iOS. (Khusus macOS, dan memerlukan penginstalan alat Xcode.)
  • Emulator Android. (Memerlukan penyiapan di Android Studio.)

3. Memulai

Mulai menggunakan Flutter

Ada berbagai cara untuk membuat project Flutter baru, Anda dapat menggunakan Android Studio dan Visual Studio Code, keduanya menyediakan alat yang diperlukan untuk tugas ini. Anda bisa mengikuti prosedur yang telah ditautkan untuk membuat project, atau menjalankan perintah berikut di terminal command line dengan mudah.

$ flutter create webview_in_flutter
Creating project webview_in_flutter...
[Listing of created files elided]
Wrote 81 files.

All done!
In order to run your application, type:

  $ cd webview_in_flutter
  $ flutter run

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

Menambahkan plugin WebView Flutter sebagai dependensi

Menambahkan kemampuan tambahan ke aplikasi Flutter adalah hal yang mudah dilakukan menggunakan paket Pub. Dalam codelab ini, Anda akan menambahkan plugin webview_flutter ke project Anda. Jalankan perintah berikut di terminal.

$ cd webview_in_flutter
$ flutter pub add webview_flutter
Resolving dependencies...
  async 2.8.1 (2.8.2 available)
  characters 1.1.0 (1.2.0 available)
  matcher 0.12.10 (0.12.11 available)
+ plugin_platform_interface 2.0.2
  test_api 0.4.2 (0.4.8 available)
  vector_math 2.1.0 (2.1.1 available)
+ webview_flutter 3.0.0
+ webview_flutter_android 2.8.0
+ webview_flutter_platform_interface 1.8.0
+ webview_flutter_wkwebview 2.7.0
Downloading webview_flutter 3.0.0...
Downloading webview_flutter_wkwebview 2.7.0...
Downloading webview_flutter_android 2.8.0...
Changed 5 dependencies!

Jika memeriksa pubspec.yaml, Anda akan melihat baris tersebut memiliki baris di bagian dependensi untuk plugin webview_flutter.

Mengonfigurasi Android minSDK

Untuk menggunakan plugin webview_flutter di Android, Anda perlu menetapkan minSDK ke 19 atau 20, bergantung pada Tampilan Platform Android yang ingin Anda gunakan. Informasi selengkapnya mengenai Tampilan Platform Android dapat ditemukan di halaman plugin webview_flutter. Ubah file android/app/build.gradle Anda sebagai berikut:

android/app/build.gradle

defaultConfig {
    // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
    applicationId "com.example.webview_in_flutter"
    minSdkVersion 20        // MODIFY
    targetSdkVersion 30
    versionCode flutterVersionCode.toInteger()
    versionName flutterVersionName
}

4. Menambahkan widget WebView ke Aplikasi Flutter

Di langkah ini, Anda akan menambahkan WebView ke aplikasi. WebView adalah tampilan native yang dihosting, dan Anda sebagai developer aplikasi memiliki pilihan untuk menghosting tampilan native ini di aplikasi Anda. Di Android, Anda memiliki pilihan antara Display Virtual, yang saat ini merupakan pilihan default untuk Android dan komposisi Hybrid. Namun, iOS selalu menggunakan komposisi Hybrid.

Untuk diskusi mendalam mengenai perbedaan antara Display Virtual dan komposisi Hybrid, harap baca dokumentasi mengenai Menghosting tampilan Android dan iOS native di aplikasi Flutter Anda dengan Tampilan Platform.

Menampilkan Webview di layar

Ganti konten lib/main.dart dengan konten berikut:

lib/main.dart

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

void main() {
  runApp(
    const MaterialApp(
      home: WebViewApp(),
    ),
  );
}

class WebViewApp extends StatefulWidget {
  const WebViewApp({Key? key}) : super(key: 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 WebView(
        initialUrl: 'https://flutter.dev',
      ),
    );
  }
}

Menjalankan kode ini di iOS atau Android akan menampilkan WebView sebagai jendela browser full bleed di perangkat Anda, yang berarti browser yang ditampilkan di perangkat Anda akan memasuki mode layar penuh tanpa batas atau margin. Saat men-scroll, Anda akan melihat bagian halaman yang mungkin terlihat sedikit aneh. Hal ini dikarenakan JavaScript masih belum aktif dan untuk merender flutter.dev dengan benar diperlukan JavaScript.

Mengaktifkan Komposisi Hybrid

Jika Anda ingin menggunakan mode Komposisi Hybrid untuk perangkat Android, Anda dapat melakukannya dengan beberapa modifikasi kecil. Ubah lib/main.dart Anda sebagai berikut:

lib/main.dart

import 'dart:io';                            // Add this import.
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

void main() {
  runApp(
    const MaterialApp(
      home: WebViewApp(),
    ),
  );
}

class WebViewApp extends StatefulWidget {
  const WebViewApp({Key? key}) : super(key: key);

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

class _WebViewAppState extends State<WebViewApp> {
  // Add from here ...
  @override
  void initState() {
    if (Platform.isAndroid) {
      WebView.platform = SurfaceAndroidWebView();
    }
    super.initState();
  }
  // ... to here.

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter WebView'),
      ),
      body: const WebView(
        initialUrl: 'https://flutter.dev',
      ),
    );
  }
}

Jangan lupa untuk mengubah minSdkVersion di build.gradle ke 19 saat Anda ingin menggunakan Tampilan Platform komposisi Hybrid.

Menjalankan aplikasi

Jalankan aplikasi Flutter di perangkat iOS atau Android untuk melihat Webview, yang menampilkan situs flutter.dev. Atau, jalankan aplikasi di emulator Android atau iOS. Jangan ragu untuk mengganti URL WebView awal dengan, misalnya, situs Anda sendiri.

$ flutter run

Dengan asumsi bahwa Anda memiliki simulator atau emulator yang sesuai, atau perangkat fisik yang terpasang, setelah mengompilasi dan men-deploy aplikasi ke perangkat, Anda akan melihat sesuatu seperti berikut:

5. Memproses peristiwa pemuatan halaman

Widget WebView menyediakan beberapa peristiwa progres pemuatan halaman, yang dapat diproses oleh aplikasi Anda. Selama siklus pemuatan halaman WebView, ada tiga peristiwa pemuatan halaman berbeda yang diaktifkan: onPageStarted, onProgress, dan onPageFinished. Pada langkah ini, Anda akan menerapkan indikator pemuatan halaman. Sebagai bonus, Anda akan ditunjukkan bahwa Anda dapat merender konten Flutter melalui area konten WebView.

Menambahkan peristiwa pemuatan halaman ke aplikasi Anda

Buat file sumber baru di lib/src/web_view_stack.dart lalu isi dengan konten berikut:

lib/src/web_view_stack.dart

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

class WebViewStack extends StatefulWidget {
  const WebViewStack({Key? key}) : super(key: key);

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

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

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        WebView(
          initialUrl: 'https://flutter.dev',
          onPageStarted: (url) {
            setState(() {
              loadingPercentage = 0;
            });
          },
          onProgress: (progress) {
            setState(() {
              loadingPercentage = progress;
            });
          },
          onPageFinished: (url) {
            setState(() {
              loadingPercentage = 100;
            });
          },
        ),
        if (loadingPercentage < 100)
          LinearProgressIndicator(
            value: loadingPercentage / 100.0,
          ),
      ],
    );
  }
}

Kode ini telah menggabungkan widget WebView di Stack, yang menempatkan WebView secara bersyarat dengan LinearProgressIndicator saat persentase pemuatan halaman kurang dari 100%. Karena kode ini melibatkan status program yang berubah dari waktu ke waktu, Anda harus menyimpan status ini di class State yang terkait dengan StatefulWidget.

Untuk memanfaatkan widget WebViewStack baru ini, ubah lib/main.dart Anda sebagai berikut:

import 'package:flutter/material.dart';
// Delete the package:webview_flutter/webview_flutter.dart import
import 'src/web_view_stack.dart';  // Add this import

void main() {
  runApp(
    const MaterialApp(
      home: WebViewApp(),
    ),
  );
}

class WebViewApp extends StatefulWidget {
  const WebViewApp({Key? key}) : super(key: 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(),   // Replace the WebView widget with WebViewStack
    );
  }
}

Saat menjalankan aplikasi, bergantung pada kondisi jaringan Anda, dan apakah browser telah meng-cache halaman yang Anda buka, Anda akan melihat indikator pemuatan halaman yang ditempatkan di atas area konten WebView.

6. Menggunakan WebViewController

Mengakses WebViewController dari Widget WebView

Widget WebView memungkinkan kontrol terprogram dengan WebViewController. Pengontrol ini tersedia setelah konstruksi widget WebView melalui callback. Ketersediaan pengontrol ini bersifat asinkron, sehingga menjadikannya kandidat utama untuk class Completer<T> asinkron Dart.

Update lib/src/web_view_stack.dart sebagai berikut:

lib/src/web_view_stack.dart

import 'dart:async';     // Add this import for Completer
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class WebViewStack extends StatefulWidget {
  const WebViewStack({required this.controller, Key? key}) : super(key: key); // Modify

  final Completer<WebViewController> controller;   // Add this attribute

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

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

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        WebView(
          initialUrl: 'https://flutter.dev',
          // Add from here ...
          onWebViewCreated: (webViewController) {
            widget.controller.complete(webViewController);
          },
          // ... to here.
          onPageStarted: (url) {
            setState(() {
              loadingPercentage = 0;
            });
          },
          onProgress: (progress) {
            setState(() {
              loadingPercentage = progress;
            });
          },
          onPageFinished: (url) {
            setState(() {
              loadingPercentage = 100;
            });
          },
        ),
        if (loadingPercentage < 100)
          LinearProgressIndicator(
            value: loadingPercentage / 100.0,
          ),
      ],
    );
  }
}

Widget WebViewStack kini menerbitkan pengontrol yang dibuat secara asinkron menggunakan Completer<WebViewController>. Penerbitan ini adalah alternatif yang lebih ringan untuk membuat argumen fungsi callback guna menyediakan pengontrol ke seluruh aplikasi.

Membuat Kontrol Navigasi

Memiliki WebView yang berfungsi adalah salah satu tujuannya, tetapi kemampuan untuk menavigasi mundur dan maju pada histori halaman dan memuat ulang halaman akan menjadi tujuan tambahan yang berguna. Untungnya, dengan WebViewController Anda dapat menambahkan fungsi ini ke aplikasi Anda.

Buat file sumber baru di lib/src/navigation_controls.dart dan isi dengan konten berikut:

lib/src/navigation_controls.dart

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

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

  final Completer<WebViewController> controller;

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<WebViewController>(
      future: controller.future,
      builder: (context, snapshot) {
        final WebViewController? controller = snapshot.data;
        if (snapshot.connectionState != ConnectionState.done ||
            controller == null) {
          return Row(
            children: const <Widget>[
              Icon(Icons.arrow_back_ios),
              Icon(Icons.arrow_forward_ios),
              Icon(Icons.replay),
            ],
          );
        }

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

Widget ini menggunakan widget FutureBuilder<T> untuk melakukan repaint dengan tepat saat pengontrol tersedia. Sambil menunggu pengontrol tersedia, ikon tiga baris akan dirender, namun setelah pengontrol muncul, pengontrol tersebut akan diganti dengan Row dari IconButton dengan handler onPressed yang menggunakan controller untuk menerapkan fungsinya.

Menambahkan kontrol navigasi ke AppBar

Dengan WebViewStack yang diupdate dan NavigationControls yang baru dibuat, kini saatnya Anda menyatukan semuanya dalam WebViewApp yang diupdate. Sebelumnya, Anda telah melihat cara menggunakan Completer<T>, tetapi bukan tempat pembuatannya. Dengan WebViewApp yang berada di dekat bagian atas hierarki Widget dalam aplikasi ini, Anda dapat membuatnya pada tingkat ini.

Update file lib/main.dart sebagai berikut:

lib/main.dart

import 'dart:async';                                    // Add this import

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';  // Add this import back

import 'src/navigation_controls.dart';                  // Add this import
import 'src/web_view_stack.dart';

void main() {
  runApp(
    const MaterialApp(
      home: WebViewApp(),
    ),
  );
}

class WebViewApp extends StatefulWidget {
  const WebViewApp({Key? key}) : super(key: key);

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

class _WebViewAppState extends State<WebViewApp> {
  final controller = Completer<WebViewController>();    // Instantiate the controller

  @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),       // Add the controller argument
    );
  }
}

Menjalankan aplikasi akan memunculkan halaman web dengan kontrol:

7. Melacak navigasi dengan NavigationDelegate

WebView memberi aplikasi Anda NavigationDelegate, yang memungkinkan aplikasi Anda melacak dan mengontrol navigasi halaman widget WebView. Saat navigasi dimulai oleh WebView, misalnya saat pengguna mengklik link, NavigationDelegate akan dipanggil. Callback NavigationDelegate dapat digunakan untuk mengontrol apakah WebView akan melanjutkan navigasi.

Mendaftarkan NavigationDelegate kustom

Pada langkah ini, Anda akan mendaftarkan callback NavigationDelegate untuk memblokir navigasi ke YouTube.com. Perhatikan bahwa penerapan sederhana ini juga akan memblokir konten YouTube inline, yang muncul di berbagai halaman dokumentasi Flutter API.

Update lib/src/web_view_stack.dart sebagai berikut:

lib/src/web_view_stack.dart

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

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

  final Completer<WebViewController> controller;

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

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

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        WebView(
          initialUrl: 'https://flutter.dev',
          onWebViewCreated: (webViewController) {
            widget.controller.complete(webViewController);
          },
          onPageStarted: (url) {
            setState(() {
              loadingPercentage = 0;
            });
          },
          onProgress: (progress) {
            setState(() {
              loadingPercentage = progress;
            });
          },
          onPageFinished: (url) {
            setState(() {
              loadingPercentage = 100;
            });
          },
          // Add from here ...
          navigationDelegate: (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.
        ),
        if (loadingPercentage < 100)
          LinearProgressIndicator(
            value: loadingPercentage / 100.0,
          ),
      ],
    );
  }
}

Pada langkah berikutnya, Anda akan menambahkan item menu untuk mengaktifkan pengujian NavigationDelegate dengan menggunakan class WebViewController. Langkah ini disiapkan sebagai latihan bagi pembaca untuk meningkatkan logika callback agar hanya memblokir navigasi halaman penuh ke YouTube.com, dan tetap mengizinkan konten YouTube inline dalam dokumentasi API.

8. Menambahkan tombol menu ke AppBar

Selama mempraktikkan beberapa langkah berikutnya, Anda akan membuat tombol menu di widget AppBar yang digunakan untuk mengevaluasi JavaScript, memanggil saluran JavaScript, dan mengelola cookie. Secara keseluruhan, ini adalah menu yang berguna.

Buat file sumber baru di lib/src/menu.dart dan isi dengan konten berikut:

lib/src/menu.dart

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

enum _MenuOptions {
  navigationDelegate,
}

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

  final Completer<WebViewController> controller;

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<WebViewController>(
      future: controller.future,
      builder: (context, controller) {
        return PopupMenuButton<_MenuOptions>(
          onSelected: (value) async {
            switch (value) {
              case _MenuOptions.navigationDelegate:
                controller.data!.loadUrl('https://youtube.com');
                break;
            }
          },
          itemBuilder: (context) => [
            const PopupMenuItem<_MenuOptions>(
              value: _MenuOptions.navigationDelegate,
              child: Text('Navigate to YouTube'),
            ),
          ],
        );
      },
    );
  }
}

Saat pengguna memilih opsi menu Buka YouTube, metode loadUrl WebViewController akan dijalankan. Navigasi ini akan diblokir oleh callback navigationDelegate yang Anda buat di langkah sebelumnya.

Untuk menambahkan menu ke layar WebViewApp, ubah lib/main.dart sebagai berikut:

lib/main.dart

import 'dart:async';

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

import 'src/menu.dart';                                // Add this import
import 'src/navigation_controls.dart';
import 'src/web_view_stack.dart';

void main() {
  runApp(
    const MaterialApp(
      home: WebViewApp(),
    ),
  );
}

class WebViewApp extends StatefulWidget {
  const WebViewApp({Key? key}) : super(key: key);

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

class _WebViewAppState extends State<WebViewApp> {
  final controller = Completer<WebViewController>();

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

Jalankan aplikasi Anda dan ketuk item menu Buka YouTube. Anda akan disambut dengan SnackBar yang menginformasikan bahwa pengontrol navigasi telah memblokir navigasi ke YouTube.

9. Mengevaluasi JavaScript

WebViewController dapat mengevaluasi ekspresi JavaScript dalam konteks halaman saat ini. Ada dua cara untuk mengevaluasi JavaScript: untuk kode JavaScript yang tidak menampilkan nilai, gunakan runJavaScript, dan untuk kode JavaScript yang menampilkan nilai, gunakan runJavaScriptReturningResult.

Untuk mengaktifkan JavaScript, Anda harus mengonfigurasi widget WebView dengan properti javaScriptMode yang disetel ke JavascriptMode.unrestricted. Secara default, javascriptMode disetel ke JavascriptMode.disabled.

Update class _WebViewStackState dengan menambahkan setelan javascriptMode sebagai berikut:

lib/src/web_view_stack.dart

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

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        WebView(
          initialUrl: 'https://flutter.dev',
          onWebViewCreated: (webViewController) {
            widget.controller.complete(webViewController);
          },
          onPageStarted: (url) {
            setState(() {
              loadingPercentage = 0;
            });
          },
          onProgress: (progress) {
            setState(() {
              loadingPercentage = progress;
            });
          },
          onPageFinished: (url) {
            setState(() {
              loadingPercentage = 100;
            });
          },
          navigationDelegate: (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;
          },
          javascriptMode: JavascriptMode.unrestricted,        // Add this line
        ),
        if (loadingPercentage < 100)
          LinearProgressIndicator(
            value: loadingPercentage / 100.0,
          ),
      ],
    );
  }
}

Setelah WebView dapat menjalankan JavaScript, Anda dapat menambahkan opsi pada menu tersebut untuk menggunakan metode runJavaScriptReturningResult.

Ubah lib/src/menu.dart sebagai berikut:

lib/src/menu.dart

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

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

  final Completer<WebViewController> controller;

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<WebViewController>(
      future: controller.future,
      builder: (context, controller) {
        return PopupMenuButton<_MenuOptions>(
          onSelected: (value) async {
            switch (value) {
              case _MenuOptions.navigationDelegate:
                controller.data!.loadUrl('https://youtube.com');
                break;
              // Add from here ...
              case _MenuOptions.userAgent:
                final userAgent = await controller.data!
                    .runJavascriptReturningResult('navigator.userAgent');
                ScaffoldMessenger.of(context).showSnackBar(SnackBar(
                  content: Text(userAgent),
                ));
                break;
              // ... to here.
            }
          },
          itemBuilder: (context) => [
            const PopupMenuItem<_MenuOptions>(
              value: _MenuOptions.navigationDelegate,
              child: Text('Navigate to YouTube'),
            ),
            // Add from here ...
            const PopupMenuItem<_MenuOptions>(
              value: _MenuOptions.userAgent,
              child: Text('Show user-agent'),
            ),
            // ... to here.
          ],
        );
      },
    );
  }
}

Jika Anda mengetuk opsi menu 'Tampilkan agen pengguna', hasil dari mengeksekusi ekspresi JavaScript navigator.userAgent akan ditampilkan dalam Snackbar. Saat menjalankan aplikasi tersebut, Anda mungkin melihat bahwa halaman Flutter.dev terlihat berbeda. Halaman ini adalah hasilnya saat JavaScript diaktifkan ketika aplikasi dijalankan.

10. Menggunakan Saluran JavaScript

JavascriptChannel memungkinkan aplikasi Anda mendaftarkan handler callback dalam konteks JavaScript WebView yang dapat dipanggil untuk mengirimkan nilai kembali ke kode Dart Aplikasi. Pada langkah ini, Anda akan mendaftarkan saluran SnackBar yang akan dipanggil dengan hasil XMLHttpRequest.

Update class WebViewStack sebagai berikut:

lib/src/web_view_stack.dart

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

  final Completer<WebViewController> controller;

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

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

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        WebView(
          initialUrl: 'https://flutter.dev',
          onWebViewCreated: (webViewController) {
            widget.controller.complete(webViewController);
          },
          onPageStarted: (url) {
            setState(() {
              loadingPercentage = 0;
            });
          },
          onProgress: (progress) {
            setState(() {
              loadingPercentage = progress;
            });
          },
          onPageFinished: (url) {
            setState(() {
              loadingPercentage = 100;
            });
          },
          navigationDelegate: (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;
          },
          javascriptMode: JavascriptMode.unrestricted,
          javascriptChannels: _createJavascriptChannels(context),  // Add this line
        ),
        if (loadingPercentage < 100)
          LinearProgressIndicator(
            value: loadingPercentage / 100.0,
          ),
      ],
    );
  }

  // Add from here ...
  Set<JavascriptChannel> _createJavascriptChannels(BuildContext context) {
    return {
      JavascriptChannel(
        name: 'SnackBar',
        onMessageReceived: (message) {
          ScaffoldMessenger.of(context)
              .showSnackBar(SnackBar(content: Text(message.message)));
        },
      ),
    };
  }
  // ... to here.
}

Untuk setiap JavascriptChannel di Set, objek saluran disediakan dalam konteks JavaScript sebagai properti jendela dengan nama yang sama seperti JavascriptChannel.name. Menggunakannya dari konteks JavaScript melibatkan pemanggilan postMessage pada JavaScriptChannel untuk mengirim pesan yang diteruskan ke handler callback onMessageReceived JavascriptChannel yang diberi nama.

Untuk memanfaatkan JavascriptChannel yang ditambahkan di atas, tambahkan item menu lain yang menjalankan XMLHttpRequest dalam konteks JavaScript dan meneruskan kembali hasilnya menggunakan SnackBar JavascriptChannel.

Setelah WebView mengetahui tentang JavascriptChannels,, Anda akan menambahkan contoh untuk memperluas aplikasi lebih lanjut. Untuk melakukannya, tambahkan PopupMenuItem tambahan ke class Menu dan tambahkan fungsi tambahan.

Update _MenuOptions dengan opsi menu tambahan, dengan menambahkan nilai enumerasi javascriptChannel, dan tambahkan implementasi ke class Menu seperti berikut:

lib/src/menu.dart

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

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

  final Completer<WebViewController> controller;

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<WebViewController>(
      future: controller.future,
      builder: (context, controller) {
        return PopupMenuButton<_MenuOptions>(
          onSelected: (value) async {
            switch (value) {
              case _MenuOptions.navigationDelegate:
                controller.data!.loadUrl('https://youtube.com');
                break;
              case _MenuOptions.userAgent:
                final userAgent = await controller.data!
                    .runJavascriptReturningResult('navigator.userAgent');
                ScaffoldMessenger.of(context).showSnackBar(SnackBar(
                  content: Text(userAgent),
                ));
                break;
              // Add from here ...
              case _MenuOptions.javascriptChannel:
                await controller.data!.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();''');
                break;
              // ... 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'),
            ),
            // Add from here ...
            const PopupMenuItem<_MenuOptions>(
              value: _MenuOptions.javascriptChannel,
              child: Text('Lookup IP Address'),
            ),
            // ... to here.
          ],
        );
      },
    );
  }
}

JavaScript ini dieksekusi saat pengguna memilih opsi menu Contoh Saluran 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();

Kode ini mengirim permintaan GET ke API Alamat IP Publik, yang menampilkan alamat IP perangkat. Hasil ini ditampilkan di SnackBar dengan memanggil postMessage yang berada di SnackBar JavascriptChannel.

11. Mengelola Cookie

Aplikasi Anda dapat mengelola cookie di WebView dengan menggunakan class CookieManager. Pada langkah ini, Anda akan menampilkan daftar cookie, menghapus daftar cookie, menghapus cookie, dan menetapkan cookie baru. Tambahkan entri ke _MenuOptions untuk setiap kasus penggunaan cookie sebagai berikut:

lib/src/menu.dart

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

Perubahan lainnya dalam langkah ini berfokus pada class Menu, termasuk konversi class Menu dari stateless menjadi stateful. Perubahan ini penting karena Menu harus memiliki CookieManager, dan status yang dapat diubah pada widget stateless adalah kombinasi yang buruk.

Dengan menggunakan Editor atau pekerjaan keyboard Anda, konversi class Menu ke StatefulWidget dan tambahkan CookieManager ke class Status yang dihasilkan sebagai berikut:

lib/src/menu.dart

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

  final Completer<WebViewController> controller;

  @override
  State<Menu> createState() => _MenuState();                  // Add this line
}

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

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

Class _MenuState akan berisi kode yang sebelumnya ditambahkan di class Menu, beserta CookieManager yang baru ditambahkan. Pada seri bagian berikutnya, Anda akan menambahkan fungsi bantuan ke _MenuState yang, pada gilirannya, akan dipanggil oleh item menu yang belum ditambahkan.

Mendapatkan daftar semua cookie

Anda akan menggunakan JavaScript untuk mendapatkan daftar semua cookie. Untuk mendapatkannya, tambahkan metode bantuan ke akhir class _MenuState, yang disebut _onListCookies. Dengan menggunakan metode runJavaScriptReturningResult, metode bantuan Anda akan mengeksekusi document.cookie dalam konteks JavaScript, yang nantinya akan menampilkan daftar semua cookie.

Tambahkan konten berikut ke class _MenuState:

lib/src/menu.dart

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

Hapus semua cookie

Untuk menghapus semua cookie di WebView, gunakan metode clearCookies dari class CookieManager. Metode ini akan menampilkan Future<bool> yang me-resolve ke true jika CookieManager menghapus cookie tersebut, dan false jika tidak ada cookie yang dihapus.

Tambahkan konten berikut ke class _MenuState:

lib/src/menu.dart

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

Menambahkan cookie dapat dilakukan dengan memanggil JavaScript. API yang digunakan untuk menambahkan Cookie ke dokumen JavaScript didokumentasikan secara mendalam di MDN.

Tambahkan konten berikut ke class _MenuState:

lib/src/menu.dart

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

Cookie juga dapat ditetapkan menggunakan CookieManager dengan cara berikut.

Tambahkan konten berikut ke class _MenuState:

lib/src/menu.dart

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

Penghapusan cookie melibatkan penambahan cookie, dengan tanggal habis masa berlaku yang telah ditetapkan sebelumnya.

Tambahkan konten berikut ke class _MenuState:

lib/src/menu.dart

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

Menambahkan item Menu CookieManager

Langkah yang tersisa hanyalah menambahkan opsi menu, dan menghubungkannya ke metode bantuan yang baru saja Anda tambahkan. Update class _MenuState sebagai berikut:

lib/src/menu.dart

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

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<WebViewController>(
      future: widget.controller.future,
      builder: (context, controller) {
        return PopupMenuButton<_MenuOptions>(
          onSelected: (value) async {
            switch (value) {
              case _MenuOptions.navigationDelegate:
                controller.data!.loadUrl('https://youtube.com');
                break;
              case _MenuOptions.userAgent:
                final userAgent = await controller.data!
                    .runJavascriptReturningResult('navigator.userAgent');
                ScaffoldMessenger.of(context).showSnackBar(SnackBar(
                  content: Text(userAgent),
                ));
                break;
              case _MenuOptions.javascriptChannel:
                await controller.data!.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();''');
                break;
              // Add from here ...
              case _MenuOptions.clearCookies:
                _onClearCookies();
                break;
              case _MenuOptions.listCookies:
                _onListCookies(controller.data!);
                break;
              case _MenuOptions.addCookie:
                _onAddCookie(controller.data!);
                break;
              case _MenuOptions.setCookie:
                _onSetCookie(controller.data!);
                break;
              case _MenuOptions.removeCookie:
                _onRemoveCookie(controller.data!);
                break;
              // ... 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'),
            ),
            // Add from here ...
            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'),
            ),
            // ... to here.
          ],
        );
      },
    );
  }

Menerapkan CookieManager

Untuk menggunakan semua fungsi yang baru saja Anda tambahkan ke aplikasi, coba langkah berikut:

  1. Pilih Cantumkan cookie. Langkah ini akan mencantumkan cookie Google Analytics yang ditetapkan oleh flutter.dev.
  2. Pilih Hapus cookie. Langkah ini akan melaporkan bahwa cookie telah dihapus.
  3. Pilih Hapus cookie lagi. Langkah ini akan melaporkan bahwa tidak ada cookie yang tersedia untuk dihapus.
  4. Pilih Cantumkan cookie. Langkah ini akan melaporkan bahwa tidak ada cookie.
  5. Pilih Tambahkan cookie. Langkah ini akan melaporkan cookie setiap kali ditambahkan.
  6. Pilih Tetapkan cookie. Langkah ini akan melaporkan bahwa cookie telah ditetapkan.
  7. Pilih Cantumkan cookie, lalu sebagai langkah terakhir, pilih Hapus cookie.

12. Memuat aset, file, dan string HTML Flutter di WebView

Aplikasi Anda dapat memuat file HTML menggunakan metode lain dan menampilkannya di WebView. Di langkah ini, Anda akan memuat aset Flutter yang ditentukan dalam file pubspec.yaml, memuat file yang berada di jalur yang ditentukan, dan memuat halaman menggunakan String HTML.

Jika ingin memuat file yang berada di jalur yang ditentukan, Anda harus menambahkan path_provider ke pubspec.yaml. Ini adalah plugin Flutter yang berfungsi untuk menemukan lokasi yang biasa digunakan pada sistem file.

Pada pubspec.yaml, tambahkan baris berikut:

pubspec.yaml

dependencies:
 flutter:
   sdk: flutter

 # The following adds the Cupertino Icons font to your application.
 # Use with the CupertinoIcons class for iOS style icons.
 cupertino_icons: ^1.0.2
 webview_flutter: ^3.0.0
 path_provider: ^2.0.7   # Add this line

Untuk memuat aset, kita harus menentukan jalur ke aset tersebut di pubspec.yaml. Di bagian pubspec.yaml, tambahkan baris berikut:

pubspec.yaml

# The following section is specific to Flutter.
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.

Untuk menambahkan aset tersebut ke project Anda, lakukan langkah-langkah berikut:

  1. Buat Direktori baru dengan nama assets dalam folder root project Anda.
  2. Buat Direktori baru dengan nama www dalam folder assets.
  3. Buat Direktori baru dengan nama styles dalam folder www.
  4. Buat File baru dengan nama index.html dalam folder www.
  5. Buat File baru dengan nama style.css dalam folder styles.

Salin dan tempel kode berikut di file 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>

Untuk style.css, gunakan beberapa baris berikut untuk menetapkan gaya header HTML:

assets/www/styles/style.css

h1 {
   color: blue;
}

Setelah aset ditetapkan dan siap digunakan, Anda dapat menerapkan metode yang diperlukan untuk memuat dan menampilkan aset, file, atau String HTML Flutter.

Memuat aset Flutter

Untuk memuat aset yang baru saja dibuat, cukup panggil metode loadFlutterAsset menggunakan WebViewController dan berikan jalur ke aset tersebut sebagai parameter. Tambahkan metode berikut di akhir kode Anda:

lib/src/menu.dart

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

Memuat file lokal

Untuk memuat file di perangkat, Anda dapat menambahkan metode yang akan menggunakan metode loadFile, sekali lagi dengan WebViewController yang menggunakan String yang berisi jalur ke file tersebut.

Anda harus membuat file yang berisi kode HTML terlebih dahulu. Anda dapat melakukannya dengan menambahkan kode HTML sebagai String di bagian atas kode dalam file menu.dart yang tepat berada di bagian bawah impor.

lib/src/menu.dart

import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
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.

Untuk membuat File dan menulis String HTML ke file tersebut, Anda akan menambahkan dua metode. _onLoadLocalFileExample akan memuat file tersebut dengan menyediakan jalur sebagai String yang ditampilkan oleh metode _prepareLocalFile(). Tambahkan metode berikut ke kode Anda:

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;
}
// ... to here.

Memuat String HTML

Untuk menampilkan halaman dengan menyediakan string HTML adalah hal yang cukup mudah. WebViewController memiliki metode yang dapat Anda gunakan bernama loadHtmlString, tempat Anda dapat memberikan String HTML sebagai argumen. WebView kemudian akan menampilkan halaman HTML yang disediakan. Tambahkan metode berikut ke kode Anda:

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.

Menambahkan item menu

Setelah aset ditetapkan dan siap digunakan, serta metode dengan semua fungsinya telah dibuat, menu dapat diperbarui. Tambahkan entri berikut ke enum _MenuOptions:

lib/src/menu.dart

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

Setelah enum diupdate, sekarang Anda dapat menambahkan opsi menu dan menghubungkannya ke metode bantuan yang baru saja Anda tambahkan. Update class _MenuState sebagai berikut:

lib/src/menu.dart

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

 @override
 Widget build(BuildContext context) {
   return FutureBuilder<WebViewController>(
     future: widget.controller.future,
     builder: (context, controller) {
       return PopupMenuButton<_MenuOptions>(
         onSelected: (value) async {
           switch (value) {
             case _MenuOptions.navigationDelegate:
               controller.data!.loadUrl('https://youtube.com');
               break;
             case _MenuOptions.userAgent:
               final userAgent = await controller.data!
                   .runJavascriptReturningResult('navigator.userAgent');
               ScaffoldMessenger.of(context).showSnackBar(SnackBar(
                 content: Text(userAgent),
               ));
               break;
             case _MenuOptions.javascriptChannel:
               await controller.data!.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();''');
               break;
             case _MenuOptions.clearCookies:
               _onClearCookies();
               break;
             case _MenuOptions.listCookies:
               _onListCookies(controller.data!);
               break;
             case _MenuOptions.addCookie:
               _onAddCookie(controller.data!);
               break;
             case _MenuOptions.setCookie:
               _onSetCookie(controller.data!);
               break;
             case _MenuOptions.removeCookie:
               _onRemoveCookie(controller.data!);
               Break;
             // Add from here ...
             case _MenuOptions.loadFlutterAsset:
               _onLoadFlutterAssetExample(controller.data!, context);
               break;
             case _MenuOptions.loadLocalFile:
               _onLoadLocalFileExample(controller.data!, context);
               break;
             case _MenuOptions.loadHtmlString:
               _onLoadHtmlStringExample(controller.data!, context);
               Break;
             // ... 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'),
           ),
           // Add from here ...
           const PopupMenuItem<_MenuOptions>(
             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.
         ],
       );
     },
   );
 }

Menguji aset, file, dan string HTML

Untuk menguji apakah kode yang baru saja diterapkan telah berfungsi dengan baik, Anda dapat menjalankan kode tersebut di perangkat dan mengklik salah satu item menu yang baru ditambahkan. Perhatikan cara _onLoadFlutterAssetExample menggunakan style.css yang ditambahkan untuk mengubah warna header file HTML menjadi biru.

13. Selesai!

Selamat. Anda telah menyelesaikan codelab. Anda dapat menemukan kode yang sudah selesai untuk codelab ini di repositori codelab.

Untuk mempelajari lebih lanjut, coba codelab Flutter lain.