Thêm WebView vào ứng dụng Flutter

1. Giới thiệu

Lần cập nhật gần đây nhất: ngày 19 tháng 10 năm 2021

Với trình bổ trợ WebView Flutter, bạn có thể thêm một tiện ích WebView vào ứng dụng Flutter trên Android hoặc iOS của mình. Trên iOS, tiện ích WebView được WKWebView hỗ trợ, còn trên Android, tiện ích WebView được WebView hỗ trợ. Trình bổ trợ này có thể hiển thị các tiện ích Flutter trên chế độ xem web. Ví dụ: bạn có thể hiển thị trình đơn thả xuống trên chế độ xem web.

Sản phẩm bạn sẽ tạo ra

Trong lớp học lập trình này, bạn sẽ từng bước xây dựng một ứng dụng di động có WebView bằng Flutter SDK. Ứng dụng này sẽ:

  • Hiển thị nội dung trên web trong WebView
  • Hiển thị các tiện ích Flutter xếp chồng trên WebView
  • Phản ứng với các sự kiện về tiến trình tải trang
  • Điều khiển WebView thông qua WebViewController
  • Chặn các trang web bằng NavigationDelegate
  • Đánh giá biểu thức JavaScript
  • Xử lý lệnh gọi lại từ JavaScript bằng JavascriptChannels
  • Đặt, xoá, thêm hoặc hiển thị cookie
  • Tải và hiển thị HTML từ các thành phần, tệp hoặc chuỗi chứa HTML

Ảnh chụp màn hình trình mô phỏng iPhone đang chạy ứng dụng Flutter có webview được nhúng cho thấy trang chủ Flutter.dev

Ảnh chụp màn hình trình mô phỏng Android đang chạy ứng dụng Flutter có webview được nhúng cho thấy trang chủ Flutter.dev

Kiến thức bạn sẽ học được

Trong lớp học lập trình này, bạn sẽ tìm hiểu cách sử dụng trình bổ trợ webview_flutter theo nhiều cách, bao gồm:

  • Cách định cấu hình trình bổ trợ webview_flutter
  • Cách theo dõi các sự kiện tiến trình tải trang
  • Cách kiểm soát hoạt động điều hướng trang
  • Cách ra lệnh cho WebView tiến và lùi trong nhật ký của nó
  • Cách đánh giá JavaScript, bao gồm cả việc sử dụng kết quả được trả về
  • Cách đăng ký lệnh gọi lại để gọi mã Dart từ JavaScript
  • Cách quản lý cookie
  • Cách tải và hiển thị các trang HTML từ thành phần hoặc tệp hoặc Chuỗi chứa HTML

Bạn cần có

2. Thiết lập môi trường phát triển Flutter

Bạn cần có 2 phần mềm để hoàn thành phòng thí nghiệm này – Flutter SDKtrình chỉnh sửa.

Bạn có thể chạy lớp học lập trình bằng bất kỳ thiết bị nào sau đây:

3. Bắt đầu

Làm quen với Flutter

Có nhiều cách để tạo một dự án Flutter mới, trong đó có cả Android StudioVisual Studio Code đều cung cấp công cụ cho nhiệm vụ này. Làm theo quy trình được liên kết để tạo một dự án hoặc thực thi các lệnh sau trong một thiết bị đầu cuối dòng lệnh thuận tiện.

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

Thêm trình bổ trợ WebView cho Flutter làm phần phụ thuộc

Bạn có thể dễ dàng thêm chức năng bổ sung vào ứng dụng Flutter bằng cách sử dụng các gói Pub. Trong lớp học lập trình này, bạn sẽ thêm trình bổ trợ webview_flutter vào dự án. Chạy các lệnh sau trong thiết bị đầu cuối.

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

Nếu kiểm tra pubspec.yaml, giờ đây bạn sẽ thấy dòng này trong mục phần phụ thuộc cho trình bổ trợ webview_flutter.

Định cấu hình Android minSDK

Để sử dụng trình bổ trợ webview_flutter trên Android, bạn cần đặt minSDK thành 20. Sửa đổi tệp android/app/build.gradle như sau:

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. Thêm tiện ích WebView vào ứng dụng Flutter

Ở bước này, bạn sẽ thêm một WebView vào ứng dụng của mình. WebView là khung hiển thị gốc được lưu trữ và trong vai trò nhà phát triển ứng dụng, bạn có thể chọn cách lưu trữ những khung hiển thị gốc này trong ứng dụng. Trên Android, bạn có thể lựa chọn giữa Màn hình ảo (hiện là chế độ mặc định cho Android) và thành phần Kết hợp. Tuy nhiên, iOS luôn sử dụng thành phần kết hợp.

Để thảo luận sâu hơn về sự khác biệt giữa Giao diện ảo và thành phần kết hợp, vui lòng đọc tài liệu về Lưu trữ chế độ xem gốc dành cho Android và iOS trong ứng dụng Flutter có Chế độ xem nền tảng.

Đặt Chế độ xem web trên màn hình

Thay thế nội dung của lib/main.dart bằng:

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

Chạy tính năng này trên iOS hoặc Android sẽ hiển thị WebView dưới dạng một cửa sổ trình duyệt tràn lề trên thiết bị của bạn. Điều này có nghĩa là trình duyệt được hiển thị trên thiết bị của bạn ở chế độ toàn màn hình mà không có bất kỳ hình thức đường viền hoặc lề nào. Khi cuộn, bạn sẽ thấy các phần của trang trông có vẻ hơi kỳ lạ. Điều này là do JavaScript hiện đang bị tắt và việc hiển thị flutter.dev yêu cầu JavaScript đúng cách.

Chạy ứng dụng

Chạy ứng dụng Flutter trên iOS hoặc Android để xem một Webview, nơi hiển thị trang web flutter.dev. Ngoài ra, bạn có thể chạy ứng dụng trong trình mô phỏng Android hoặc trình mô phỏng iOS. Bạn có thể thay thế URL WebView ban đầu bằng trang web của riêng bạn, ví dụ như URL đó.

$ flutter run

Giả sử bạn đã chạy trình mô phỏng hoặc trình mô phỏng thích hợp hay đi kèm một thiết bị thực, sau khi biên dịch và triển khai ứng dụng cho thiết bị của mình, bạn sẽ thấy như sau:

Ảnh chụp màn hình trình mô phỏng iPhone đang chạy ứng dụng Flutter có webview được nhúng cho thấy trang chủ Flutter.dev

Ảnh chụp màn hình trình mô phỏng Android đang chạy ứng dụng Flutter có webview được nhúng cho thấy trang chủ Flutter.dev

5. Theo dõi các sự kiện tải trang

Tiện ích WebView cung cấp một số sự kiện tiến trình tải trang mà ứng dụng của bạn có thể theo dõi. Trong chu kỳ tải trang WebView, có 3 sự kiện tải trang sẽ được kích hoạt: onPageStarted, onProgressonPageFinished. Ở bước này, bạn sẽ triển khai chỉ báo tải trang. Ngoài ra, việc này còn cho thấy rằng bạn có thể kết xuất nội dung trên Flutter trên vùng nội dung WebView.

Thêm sự kiện tải trang vào ứng dụng

Tạo một tệp nguồn mới tại lib/src/web_view_stack.dart và điền nội dung sau vào tệp đó:

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

Mã này đã gói tiện ích WebView trong một Stack, phủ lên WebView bằng một LinearProgressIndicator theo điều kiện khi tỷ lệ phần trăm tải trang nhỏ hơn 100%. Do trạng thái này liên quan đến trạng thái chương trình thay đổi theo thời gian, nên bạn đã lưu trữ trạng thái này trong một lớp State liên kết với StatefulWidget.

Để sử dụng tiện ích WebViewStack mới này, hãy sửa đổi lib/main.dart của bạn như sau:

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

Khi bạn chạy ứng dụng, tuỳ thuộc vào điều kiện mạng và việc trình duyệt có lưu trang mà bạn đang chuyển đến vào bộ nhớ đệm hay không, bạn sẽ thấy chỉ báo tải trang phủ lên trên vùng nội dung WebView.

6. Làm việc với WebViewController

Truy cập WebViewController từ Tiện ích WebView

Tiện ích WebView cho phép kiểm soát có lập trình bằng WebViewController. Bộ điều khiển này sẽ được cung cấp sau khi xây dựng tiện ích WebView thông qua một lệnh gọi lại. Bản chất không đồng bộ của khả năng sử dụng của bộ điều khiển này khiến bộ điều khiển này trở thành lựa chọn hàng đầu cho lớp Completer<T> không đồng bộ của Dart.

Cập nhật lib/src/web_view_stack.dart như sau:

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

Tiện ích WebViewStack hiện sử dụng bộ điều khiển được tạo trong tiện ích xung quanh. Việc này sẽ cho phép chia sẻ bộ điều khiển WebViewWidget với các phần khác của ứng dụng một cách dễ dàng.

Tạo các nút điều khiển điều hướng

Việc có WebView hoạt động là một điều hữu ích, nhưng khả năng điều hướng ngược và tiến trong nhật ký trang và tải lại trang thì sẽ là một cách bổ sung hữu ích. Rất may, với WebViewController, bạn có thể thêm chức năng này vào ứng dụng của mình.

Tạo một tệp nguồn mới tại lib/src/navigation_controls.dart và điền vào tệp đó như sau:

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

Tiện ích này sử dụng WebViewController được chia sẻ với nó tại thời điểm xây dựng để cho phép người dùng điều khiển WebView thông qua một loạt IconButton.

Thêm các tuỳ chọn điều khiển điều hướng vào AppBar

Với WebViewStack đã cập nhật và NavigationControls mới được chế tạo trong tay, đã đến lúc bạn tổng hợp tất cả trong một WebViewApp mới. Đây là nơi chúng ta tạo WebViewController dùng chung. Với WebViewApp ở gần đầu cây Tiện ích trong ứng dụng này, bạn nên tạo nó ở cấp này.

Cập nhật tệp lib/main.dart như sau:

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

Khi chạy ứng dụng, bạn sẽ thấy một trang web có các chế độ điều khiển:

Ảnh chụp màn hình trình mô phỏng iPhone đang chạy ứng dụng Flutter với một webview được nhúng cho thấy trang chủ Flutter.dev kèm theo các chế độ kiểm soát trang trước, trang tiếp theo và tải lại trang

Ảnh chụp màn hình trình mô phỏng Android đang chạy ứng dụng Flutter có một webview được nhúng cho thấy trang chủ Flutter.dev kèm theo các chế độ kiểm soát trang trước, trang tiếp theo và tải lại trang

7. Theo dõi hoạt động điều hướng bằng NavigationDelegate

WebView cung cấp cho ứng dụng của bạn một NavigationDelegate, cho phép ứng dụng đó theo dõi và kiểm soát việc điều hướng trang của tiện ích WebView. Khi WebView, khởi tạo một thao tác điều hướng, chẳng hạn như khi người dùng nhấp vào một đường liên kết, NavigationDelegate sẽ được gọi. Bạn có thể sử dụng lệnh gọi lại NavigationDelegate để kiểm soát xem WebView có tiếp tục điều hướng hay không.

Đăng ký một NavigationDelegate tuỳ chỉnh

Trong bước này, bạn sẽ đăng ký một lệnh gọi lại NavigationDelegate để chặn điều hướng đến YouTube.com. Lưu ý: Cách triển khai đơn giản này cũng chặn nội dung trên YouTube cùng dòng, vốn xuất hiện trên nhiều trang tài liệu về Flutter API.

Cập nhật lib/src/web_view_stack.dart như sau:

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

Trong bước tiếp theo, bạn sẽ thêm một mục trong trình đơn để cho phép kiểm thử NavigationDelegate bằng cách sử dụng lớp WebViewController. Đây chỉ là một bài tập dành cho người đọc nhằm tăng cường logic của lệnh gọi lại để chỉ chặn điều hướng toàn trang đến YouTube.com mà vẫn cho phép nội dung YouTube cùng dòng trong tài liệu về API.

8. Thêm một nút trình đơn vào AppBar

Trong vài bước tiếp theo, bạn sẽ tạo một nút trình đơn trong tiện ích AppBar dùng để đánh giá JavaScript, gọi các kênh JavaScript và quản lý cookie. Nói chung, một trình đơn thực sự hữu ích.

Tạo một tệp nguồn mới tại lib/src/menu.dart và điền vào tệp đó như sau:

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

Khi người dùng chọn tuỳ chọn trình đơn Navigate to YouTube (Di chuyển đến YouTube), phương thức loadRequest của WebViewController sẽ được thực thi. Thao tác điều hướng này sẽ bị chặn bởi lệnh gọi lại navigationDelegate mà bạn đã tạo ở bước trước.

Để thêm trình đơn vào màn hình của WebViewApp, hãy sửa đổi lib/main.dart như sau:

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

Chạy ứng dụng rồi nhấn vào mục trong trình đơn Navigate to YouTube (Di chuyển đến YouTube). Bạn sẽ được chào đón bằng SnackBar thông báo rằng bộ điều khiển điều hướng đã chặn khi điều hướng đến YouTube.

Ảnh chụp màn hình trình mô phỏng Android đang chạy ứng dụng Flutter có webview được nhúng cho thấy trang chủ Flutter.dev có một mục trong trình đơn cho thấy tuỳ chọn &quot;Navigate to YouTube&quot; (Di chuyển đến YouTube)

Ảnh chụp màn hình trình mô phỏng Android đang chạy ứng dụng Flutter có webview được nhúng cho thấy trang chủ Flutter.dev có thông báo ngắn bật lên có nội dung &quot;Chặn tính năng điều hướng đến m.youtube.com&quot;

9. Đánh giá JavaScript

WebViewController có thể đánh giá biểu thức JavaScript trong ngữ cảnh của trang hiện tại. Có hai cách để đánh giá JavaScript: đối với mã JavaScript không trả về giá trị, hãy sử dụng runJavaScript và đối với mã JavaScript trả về giá trị, hãy sử dụng runJavaScriptReturningResult.

Để bật JavaScript, bạn cần định cấu hình WebViewController bằng thuộc tính javaScriptMode được đặt thành JavascriptMode.unrestricted. Theo mặc định, javascriptMode được đặt thành JavascriptMode.disabled.

Cập nhật lớp _WebViewStackState bằng cách thêm chế độ cài đặt javascriptMode như sau:

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

Giờ đây, WebViewWidget có thể thực thi JavaScript, bạn có thể thêm một tuỳ chọn vào trình đơn để sử dụng phương thức runJavaScriptReturningResult.

Khi sử dụng Trình chỉnh sửa hoặc một số hoạt động của bàn phím, hãy chuyển đổi lớp Trình đơn thành một StatefulWidget. Sửa đổi lib/src/menu.dart để phù hợp với mục sau:

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

Khi bạn nhấn vào liên kết "Hiển thị tác nhân người dùng" trình đơn, thì kết quả của việc thực thi biểu thức JavaScript navigator.userAgent sẽ được hiển thị trong Snackbar. Khi chạy ứng dụng, bạn có thể nhận thấy trang Flutter.dev trông có vẻ khác. Đây là kết quả của quá trình chạy khi bật JavaScript.

Ảnh chụp màn hình trình mô phỏng iPhone đang chạy ứng dụng Flutter có webview được nhúng cho thấy trang chủ Flutter.dev có một mục trong trình đơn cho thấy các lựa chọn &quot;Navigate to YouTube&quot; (Di chuyển đến YouTube) hoặc &quot;Hiển thị tác nhân người dùng&quot;

Ảnh chụp màn hình trình mô phỏng iPhone đang chạy ứng dụng Flutter có webview được nhúng cho thấy trang chủ Flutter.dev cùng thông báo ngắn bật lên cho thấy chuỗi tác nhân người dùng.

10. Làm việc với kênh JavaScript

Kênh JavaScript cho phép ứng dụng của bạn đăng ký trình xử lý gọi lại trong ngữ cảnh JavaScript của WebViewWidget. Trình xử lý này có thể được gọi để truyền các giá trị về mã Dart của ứng dụng. Ở bước này, bạn sẽ đăng ký kênh SnackBar. Kênh này sẽ được gọi bằng kết quả của XMLHttpRequest.

Cập nhật lớp WebViewStack như sau:

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

Đối với mỗi Kênh JavaScript trong Set, một đối tượng kênh sẽ được cung cấp trong ngữ cảnh JavaScript dưới dạng thuộc tính cửa sổ có tên giống với tên của Kênh JavaScript name. Việc sử dụng thuộc tính này từ ngữ cảnh JavaScript bao gồm việc gọi postMessage trên Kênh JavaScript để gửi thông báo được chuyển đến trình xử lý gọi lại onMessageReceived của JavascriptChannel đã được đặt tên.

Để sử dụng Kênh JavaScript đã thêm ở trên, hãy thêm một mục khác trong trình đơn thực thi XMLHttpRequest trong ngữ cảnh JavaScript và trả về kết quả bằng Kênh JavaScript SnackBar.

Giờ đây WebViewWidget đã biết về Kênh JavaScript của chúng tôi,,bạn sẽ thêm một ví dụ để mở rộng ứng dụng hơn nữa. Để thực hiện việc này, hãy thêm một PopupMenuItem bổ sung vào lớp Menu và thêm chức năng bổ sung.

Cập nhật _MenuOptions bằng tuỳ chọn trình đơn bổ sung bằng cách thêm giá trị liệt kê javascriptChannel và thêm một phương thức triển khai cho lớp Menu như sau:

lib/src/menu.dart

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

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

  final WebViewController controller;

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

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

JavaScript này được thực thi khi người dùng chọn tùy chọn trình đơn Ví dụ về kênh 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();

Mã này sẽ gửi yêu cầu GET đến một API Địa chỉ IP công khai và trả về địa chỉ IP của thiết bị. Kết quả này được hiển thị trong SnackBar bằng cách gọi postMessage trên SnackBar JavascriptChannel.

11. Quản lý cookie

Ứng dụng của bạn có thể quản lý cookie trong WebView bằng cách sử dụng lớp CookieManager. Trong bước này, bạn sẽ hiển thị danh sách cookie, xoá danh sách cookie, xoá cookie và đặt cookie mới. Thêm các mục nhập vào _MenuOptions cho từng trường hợp sử dụng cookie như sau:

lib/src/menu.dart

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

Những thay đổi còn lại trong bước này tập trung vào lớp Menu, bao gồm cả việc chuyển đổi lớp Menu từ không có trạng thái sang có trạng thái. Thay đổi này rất quan trọng vì Menu cần sở hữu CookieManager và trạng thái có thể thay đổi trong các tiện ích không có trạng thái là một cách kết hợp không hợp lệ.

Thêm CookieManager vào lớp Trạng thái thu được như sau:

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

Lớp _MenuState sẽ chứa mã đã thêm trước đó vào lớp Menu, cùng với CookieManager mới được thêm vào. Trong loạt phần tiếp theo, bạn sẽ thêm các chức năng trợ giúp vào _MenuState. Các chức năng này sẽ được gọi bằng các mục chưa được thêm trong trình đơn.

Nhận danh sách tất cả cookie

Bạn sẽ sử dụng JavaScript để lấy danh sách tất cả cookie. Để làm được việc này, hãy thêm một phương thức trợ giúp vào cuối lớp _MenuState, có tên là _onListCookies. Khi sử dụng phương thức runJavaScriptReturningResult, phương thức trợ giúp của bạn sẽ thực thi document.cookie trong ngữ cảnh JavaScript và trả về danh sách tất cả cookie.

Thêm nội dung sau đây vào lớp _MenuState:

lib/src/menu.dart

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

Xoá tất cả cookie

Để xoá tất cả cookie trong WebView, hãy sử dụng phương thức clearCookies của lớp CookieManager. Phương thức này trả về một Future<bool> phân giải thành true nếu CookieManager xoá cookie và false nếu không có cookie nào để xoá.

Thêm nội dung sau đây vào lớp _MenuState:

lib/src/menu.dart

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

Có thể thêm cookie bằng cách gọi JavaScript. API dùng để thêm Cookie vào tài liệu JavaScript được ghi lại chi tiết trên MDN.

Thêm nội dung sau đây vào lớp _MenuState:

lib/src/menu.dart

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

Bạn cũng có thể đặt cookie bằng cách sử dụng CookieManager như sau.

Thêm nội dung sau đây vào lớp _MenuState:

lib/src/menu.dart

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

Việc xoá cookie bao gồm cả việc thêm một cookie, có ngày hết hạn được đặt trong quá khứ.

Thêm nội dung sau đây vào lớp _MenuState:

lib/src/menu.dart

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

Thêm các mục trong Trình đơn CookieManager

Tất cả việc còn lại là thêm các tuỳ chọn trong trình đơn và truyền chúng vào các phương thức trợ giúp bạn vừa thêm. Cập nhật lớp _MenuState như sau:

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

Thực hành CookieManager

Để sử dụng tất cả chức năng bạn vừa thêm vào ứng dụng, hãy thử các bước sau:

  1. Chọn Liệt kê cookie. Trang này sẽ liệt kê các cookie Google Analytics do flutter.dev thiết lập.
  2. Chọn Xoá cookie. Hệ thống sẽ báo cáo rằng cookie thực sự đã bị xoá.
  3. Chọn Xoá cookie một lần nữa. Sẽ báo cáo rằng không có cookie nào để xoá.
  4. Chọn Liệt kê cookie. Hộp thoại sẽ báo cáo rằng không có cookie.
  5. Chọn Thêm cookie. Thao tác này sẽ báo cáo cookie là đã được thêm.
  6. Chọn Đặt cookie. Cookie sẽ báo cáo là đã đặt.
  7. Chọn Liệt kê cookie và sau đó khi phát triển cuối cùng, chọn Remove cookie (Xoá cookie).

Ảnh chụp màn hình trình mô phỏng Android đang chạy ứng dụng Flutter có webview được nhúng, hiển thị trang chủ Flutter.dev với danh sách các tuỳ chọn trình đơn bao gồm cách truy cập vào YouTube, cho thấy tác nhân người dùng và tương tác với kho cookie của trình duyệt

Ảnh chụp màn hình trình mô phỏng Android đang chạy ứng dụng Flutter có webview được nhúng cho thấy trang chủ Flutter.dev cùng với một cửa sổ bật lên thông báo ngắn cho thấy các cookie đã được đặt trong trình duyệt

Ảnh chụp màn hình trình mô phỏng Android đang chạy ứng dụng Flutter có một webview được nhúng cho thấy trang chủ Flutter.dev cùng với một cửa sổ bật lên có thông báo ngắn với nội dung &quot;Có cookie. Nay chúng đã biến mất!&#39;

Ảnh chụp màn hình trình mô phỏng Android đang chạy ứng dụng Flutter có webview được nhúng cho thấy trang chủ Flutter.dev cùng với một cửa sổ bật lên thông báo ngắn có nội dung &quot;Custom cookie added&quot;.

12. Tải các thành phần, tệp và chuỗi HTML của Flutter trong WebView

Ứng dụng của bạn có thể tải tệp HTML bằng nhiều phương thức và hiển thị những tệp đó trong WebView. Ở bước này, bạn sẽ tải một thành phần Flutter được chỉ định trong tệp pubspec.yaml, sau đó tải một tệp nằm tại đường dẫn được chỉ định và tải một trang bằng một Chuỗi HTML.

Nếu muốn tải một tệp nằm ở một đường dẫn cụ thể, bạn cần thêm path_provider vào pubspec.yaml. Đây là một trình bổ trợ Flutter dùng để tìm các vị trí thường dùng trên hệ thống tệp.

Trên dòng lệnh, hãy chạy lệnh sau:

$ flutter pub add path_provider

Để tải thành phần, chúng ta cần chỉ định đường dẫn đến thành phần đó trong pubspec.yaml. Trong pubspec.yaml, hãy thêm các dòng sau:

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.

Để thêm các thành phần vào dự án, hãy làm theo các bước sau:

  1. Tạo một Directory (Thư mục) mới có tên assets trong thư mục gốc của dự án.
  2. Tạo một Directory (Thư mục) mới có tên www trong thư mục assets.
  3. Tạo một Directory (Thư mục) mới có tên styles trong thư mục www.
  4. Tạo một Tệp mới có tên là index.html trong thư mục www.
  5. Tạo một Tệp mới có tên là style.css trong thư mục styles.

Sao chép và dán mã nguồn sau đây vào tệp 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>

Đối với style.css, hãy sử dụng vài dòng sau để thiết lập kiểu tiêu đề HTML:

assets/www/styles/style.css

h1 {
  color: blue;
}

Giờ đây, các thành phần đã được thiết lập và sẵn sàng để sử dụng, bạn có thể triển khai các phương thức cần thiết để tải và hiển thị các thành phần, tệp hoặc chuỗi HTML của Flutter.

Tải thành phần Flutter

Để tải thành phần bạn vừa tạo, bạn chỉ cần gọi phương thức loadFlutterAsset bằng cách sử dụng WebViewController và cung cấp cho tham số đường dẫn đến thành phần đó. Thêm phương thức sau vào cuối mã:

lib/src/menu.dart

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

Tải tệp trên máy

Để tải một tệp trên thiết bị, bạn có thể thêm một phương thức sẽ sử dụng phương thức loadFile, một lần nữa bằng cách sử dụng WebViewController để lấy String chứa đường dẫn đến tệp.

Trước tiên, bạn cần tạo một tệp có chứa mã HTML. Bạn chỉ cần thực hiện việc này bằng cách thêm mã HTML dưới dạng Chuỗi ở đầu mã trong tệp menu.dart ngay bên dưới mục nhập.

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.

Để tạo một File và ghi Chuỗi HTML vào tệp, bạn sẽ thêm 2 phương thức. _onLoadLocalFileExample sẽ tải tệp bằng cách cung cấp đường dẫn dưới dạng Chuỗi được phương thức _prepareLocalFile() trả về. Thêm các phương thức sau vào mã của bạn:

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

Tải chuỗi HTML

Để hiển thị một trang bằng cách cung cấp chuỗi HTML thì khá đơn giản. WebViewController có một phương thức mà bạn có thể dùng là loadHtmlString, trong đó bạn có thể cung cấp Chuỗi HTML làm đối số. Sau đó, WebView sẽ hiển thị trang HTML được cung cấp. Thêm phương thức sau vào mã của bạn:

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.

Thêm các món trong thực đơn

Giờ đây, các thành phần đã được thiết lập và sẵn sàng để sử dụng, cũng như các phương thức có tất cả chức năng đã được tạo, bạn có thể cập nhật trình đơn. Thêm các mục sau vào enum _MenuOptions:

lib/src/menu.dart

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

Giờ đây, khi enum đã được cập nhật, bạn có thể thêm các tuỳ chọn trong trình đơn và chuyển các tuỳ chọn đó vào các phương thức trợ giúp mà bạn vừa thêm. Cập nhật lớp _MenuState như sau:

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

Thử nghiệm thành phần, tệp và chuỗi HTML

Để kiểm tra xem mã có hoạt động mà bạn vừa triển khai hay không, bạn có thể chạy mã trên thiết bị rồi nhấp vào một trong các mục trình đơn mới được thêm vào. Hãy lưu ý cách _onLoadFlutterAssetExample sử dụng style.css mà chúng ta đã thêm để thay đổi tiêu đề của tệp HTML thành màu xanh dương.

Ảnh chụp màn hình trình mô phỏng Android đang chạy ứng dụng Flutter có chế độ xem web được nhúng cho thấy một trang có nhãn &quot;Local demo page&quot; với tiêu đề màu xanh dương

Ảnh chụp màn hình trình mô phỏng Android đang chạy ứng dụng Flutter có chế độ xem web được nhúng cho thấy một trang có nhãn &quot;Local demo page&quot; với tiêu đề màu đen

13. Đã xong!

Xin chúc mừng!!! Bạn đã hoàn tất lớp học lập trình. Bạn có thể tìm thấy mã đã hoàn tất cho lớp học lập trình này trong kho lưu trữ lớp học lập trình.

Để tìm hiểu thêm, hãy thử tham gia các lớp học lập trình khác về Flutter.