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 Android hoặc iOS. Trên iOS, tiện ích WebView được hỗ trợ bởi WKWebView, còn trên Android, tiện ích WebView được hỗ trợ bởi WebView. Trình bổ trợ này có thể kết xuất các tiện ích Flutter trên chế độ xem web. Ví dụ: bạn có thể hiển thị một 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 web trong
WebView
- Hiển thị các tiện ích Flutter xếp chồng lên nhau trên
WebView
- Phản ứng với các sự kiện tiến trình tải trang
- Kiểm soát
WebView
thông quaWebViewController
- 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 cookie
- Tải và hiển thị HTML từ các thành phần, tệp hoặc chuỗi chứa HTML
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 điều khiển
WebView
để di chuyển qua lại trong nhật ký - 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, tệp hoặc chuỗi chứa HTML
Bạn cần có
- Android Studio 4.1 trở lên (để phát triển ứng dụng Android)
- Xcode 12 trở lên (để phát triển iOS)
- SDK Flutter
- Một trình soạn thảo mã, chẳng hạn như Android Studio hoặc Visual Studio Code.
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 bài thực hành này: Flutter SDK và một trình chỉnh sửa.
Bạn có thể chạy lớp học lập trình này bằng bất kỳ thiết bị nào sau đây:
- Một thiết bị Android hoặc iOS thực được kết nối với máy tính và được đặt ở Chế độ nhà phát triển.
- Trình mô phỏng iOS (bạn cần cài đặt các công cụ Xcode).
- Trình mô phỏng Android (cần thiết lập trong Android Studio).
3. Bắt đầu
Làm quen với Flutter
Có nhiều cách để tạo một dự án Flutter mới, cả Android Studio và Visual Studio Code đều cung cấp công cụ cho tác vụ này. Hãy làm theo các 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 tiện dụng.
$ 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 Flutter làm phần phụ thuộc
Cách tốt nhất để thêm chức năng bổ sung vào ứng dụng Flutter là 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 của mình. Chạy các lệnh sau trong dòng lệnh.
$ cd webview_in_flutter $ flutter pub add webview_flutter Resolving dependencies... Downloading packages... collection 1.18.0 (1.19.0 available) leak_tracker 10.0.5 (10.0.7 available) leak_tracker_flutter_testing 3.0.5 (3.0.7 available) material_color_utilities 0.11.1 (0.12.0 available) + plugin_platform_interface 2.1.8 string_scanner 1.2.0 (1.3.0 available) test_api 0.7.2 (0.7.3 available) + webview_flutter 4.9.0 + webview_flutter_android 3.16.7 + webview_flutter_platform_interface 2.10.0 + webview_flutter_wkwebview 3.15.0 Changed 5 dependencies! 6 packages have newer versions incompatible with dependency constraints. Try `flutter pub outdated` for more information.
Nếu kiểm tra pubspec.yaml, bạn sẽ thấy tệp này có một dòng trong phần dependencies (phần phụ thuộc) cho trình bổ trợ webview_flutter
.
Định cấu hình minSDK Android
Để 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
Trong bước này, bạn sẽ thêm một WebView
vào ứng dụng của mình. WebView là các khung hiển thị tích hợp được lưu trữ và bạn (nhà phát triển ứng dụng) có thể chọn cách lưu trữ các khung hiển thị tích hợp này trong ứng dụng của mình. Trên Android, bạn có thể chọn giữa Màn hình ảo (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 Hybrid.
Để biết thông tin chi tiết về sự khác biệt giữa Màn hình ảo và Thành phần kết hợp, hãy đọc tài liệu về Lưu trữ khung hiển thị Android và iOS gốc trong ứng dụng Flutter bằng Khung hiển thị nền tảng .
Đặt một Webview trên màn hình
Thay thế nội dung của lib/main.dart
bằng nội dung sau:
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,
),
);
}
}
Khi chạy trên iOS hoặc Android, thao tác này sẽ hiển thị WebView dưới dạng 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 sẽ xuất hiện trên thiết bị ở chế độ toàn màn hình mà không có bất kỳ đường viền hoặc lề nào. Khi cuộn, bạn sẽ nhận thấy những phần trông có vẻ hơi lạ trên trang. Điều này là do JavaScript bị tắt và việc kết xuất flutter.dev đúng cách đòi hỏi phải có JavaScript.
Chạy ứng dụng
Chạy ứng dụng Flutter trên iOS hoặc Android để xem một WebView hiển thị trang web flutter.dev. Ngoài ra, hãy 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 mình.
$ flutter run
Giả sử bạn đang chạy trình mô phỏng thích hợp hoặc đã đính kèm một thiết bị thực, sau khi biên dịch và triển khai ứng dụng vào thiết bị, bạn sẽ thấy một số nội dung như sau:
5. Theo dõi 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 khác nhau được kích hoạt: onPageStarted
, onProgress
và onPageFinished
. Ở bước này, bạn sẽ triển khai một chỉ báo tải trang. Ngoài ra, thao tác này sẽ cho thấy rằng bạn có thể kết xuất nội dung 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 đã bao bọc tiện ích WebView
trong một Stack
, có điều kiện phủ lớp WebView
bằng một LinearProgressIndicator
khi tỷ lệ phần trăm tải trang nhỏ hơn 100%. Vì điều 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ạng thái này trong một lớp State
được liên kết với một StatefulWidget
.
Để sử dụng tiện ích WebViewStack
mới này, hãy sửa đổi lib/main.dart
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 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 được phủ lên trên vùng nội dung WebView
.
6. Xử lý WebViewController
Truy cập vào WebViewController từ Tiện ích WebView
Tiện ích WebView
cho phép kiểm soát theo phương thức lập trình bằng WebViewController
. Bộ điều khiển này được cung cấp sau khi tạo 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 tính khả dụng của bộ điều khiển này khiến nó trở thành ứng cử viê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 một bộ điều khiển được tạo trong tiện ích xung quanh. Thao tác này sẽ cho phép chia sẻ bộ điều khiển cho WebViewWidget
với các phần khác của ứng dụng.
Tạo các nút điều khiển thao tác
Có một WebView
đang hoạt động là một chuyện, nhưng có thể điều hướng qua lại trong nhật ký trang và tải lại trang sẽ là một bộ tính năng bổ sung hữu ích. Rất may là bạn có thể thêm chức năng này vào ứng dụng của mình bằng WebViewController
.
Tạo một tệp nguồn mới tại lib/src/navigation_controls.dart
và điền nội dung sau vào tệp đó:
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 tiện ích tại thời điểm tạo để cho phép người dùng kiểm soát WebView
thông qua một loạt IconButton
.
Thêm các nút điều khiển thao tác vào AppBar
Với WebViewStack
đã được cập nhật và NavigationControls
mới được tạo, giờ là lúc bạn kết hợp tất cả vào một WebViewApp
đã được cập nhật. Đâ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 tiện ích ở 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:
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 thao tác điều hướng trang của tiện ích WebView
. Khi một thao tác điều hướng được WebView,
bắt đầu, 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ể dùng lệnh gọi lại NavigationDelegate
để kiểm soát việc WebView
có tiếp tục chỉ đườ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 thao tác chuyển đến YouTube.com. Xin lưu ý rằng việc triển khai đơn giản này cũng chặn nội dung YouTube cùng dòng xuất hiện trên nhiều trang tài liệu về API Flutter.
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 trình đơn để kiểm thử NavigationDelegate
bằng cách sử dụng lớp WebViewController
. Bạn có thể tự mình bổ sung logic của lệnh gọi lại để chỉ chặn thao tác điều hướng toàn trang đến YouTube.com, đồng thời vẫn cho phép nội dung YouTube cùng dòng trong tài liệu API.
8. Thêm nút trình đơn vào AppBar
Trong một số bước tiếp theo, bạn sẽ tạo một nút trình đơn trong tiện ích AppBar
. Nút này được dùng để đánh giá JavaScript, gọi các kênh JavaScript và quản lý cookie. Nhìn chung, đây là một trình đơn hữu ích.
Tạo một tệp nguồn mới tại lib/src/menu.dart
và điền nội dung sau vào tệp đó:
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 mục trình đơn Chuyển đến YouTube, phương thức loadRequest
của WebViewController
sẽ được thực thi. Hoạt động đ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 Navigate to YouTube (Chuyển đến YouTube) trong trình đơn. Bạn sẽ thấy một SnackBar thông báo rằng trình điều khiển điều hướng đã chặn việc chuyển đến YouTube.
9. Đánh giá JavaScript
WebViewController
có thể đánh giá các 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 có trả về giá trị, hãy sử dụng runJavaScriptReturningResult
.
Để bật JavaScript, bạn cần định cấu hình WebViewController
với 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, khi WebViewWidget
có thể thực thi JavaScript, bạn có thể thêm một lựa chọn vào trình đơn để sử dụng phương thức runJavaScriptReturningResult
.
Bằng cách sử dụng Trình chỉnh sửa hoặc một số thao tác trên bàn phím, hãy chuyển đổi lớp Trình đơn thành StatefulWidget. Sửa đổi lib/src/menu.dart
cho phù hợp với nội dung 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 lựa chọn trình đơn "Hiện user-agent", kết quả thực thi biểu thức JavaScript navigator.userAgent
sẽ xuất hiện trong Snackbar
. Khi chạy ứng dụng, bạn có thể nhận thấy trang Flutter.dev trông khác. Đây là kết quả của việc chạy khi JavaScript đang bật.
10. Làm việc với các kênh JavaScript
Các kênh JavaScript cho phép ứng dụng của bạn đăng ký trình xử lý lệnh gọi lại trong bối cảnh JavaScript của WebViewWidget
. Trình xử lý này có thể được gọi để truyền các giá trị trở lại mã Dart của Ứng dụng. Ở bước này, bạn sẽ đăng ký một kênh SnackBar
sẽ được gọi bằng kết quả của một 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ó sẵn trong ngữ cảnh JavaScript dưới dạng một thuộc tính cửa sổ có cùng tên với Kênh JavaScript name
. Việc sử dụng phương thức này từ ngữ cảnh JavaScript liên quan đến việc gọi postMessage
trên Kênh JavaScript để gửi một thông báo được truyền đến trình xử lý lệnh gọi lại onMessageReceived
của JavascriptChannel
có tên.
Để sử dụng Kênh JavaScript mà bạn đã thêm trước đó, hãy thêm một mục trình đơn khác thực thi một XMLHttpRequest
trong ngữ cảnh JavaScript và truyền kết quả trở lại bằng Kênh JavaScript SnackBar
.
Giờ đây, khi WebViewWidget
đã biết về các kênh JavaScript của chúng ta,
, bạn sẽ thêm một ví dụ để mở rộng ứng dụng hơn nữa. Để làm 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 lựa chọn bổ sung trong trình đơn bằng cách thêm giá trị liệt kê javascriptChannel
và thêm một phương thức triển khai vào 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 sẽ được thực thi khi người dùng chọn mục 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 gửi một yêu cầu GET
đến một API địa chỉ IP công khai, trả về địa chỉ IP của thiết bị. Kết quả này xuất hiện trong một 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 danh sách cookie, xoá danh sách cookie, xoá cookie và đặt cookie mới. Thêm các mục 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 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 trạng thái là một sự kết hợp không tốt.
Thêm CookieManager vào lớp State kết quả 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ã đã được thêm trước đó trong lớp Menu
, cùng với CookieManager
mới được thêm. Trong chuỗi các phần tiếp theo, bạn sẽ thêm các hàm trợ giúp vào _MenuState
. Các hàm này sẽ được gọi bởi các mục trong trình đơn chưa được thêm.
Lấy danh sách tất cả cookie
Bạn sẽ dùng JavaScript để lấy danh sách tất cả cookie. Để thực hiện 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
. Bằng cách 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, trả về danh sách tất cả cookie.
Thêm nội dung sau 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 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),
),
);
}
Thêm cookie
Bạn 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 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.'),
),
);
}
Đặt cookie bằng CookieManager
Bạn cũng có thể đặt cookie bằng CookieManager như sau.
Thêm nội dung sau 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.'),
),
);
}
Xoá cookie
Để xoá một cookie, bạn cần thêm một cookie có ngày hết hạn được đặt trong quá khứ.
Thêm nội dung sau 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ả những gì còn lại là thêm các lựa chọn trong trình đơn và liên kết chúng với 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: // 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 hiện CookieManager
Để sử dụng tất cả chức năng mà bạn vừa thêm vào ứng dụng, hãy thử các bước sau:
- Chọn Liệt kê cookie. Thao tác này sẽ liệt kê các cookie Google Analytics do flutter.dev đặt.
- Chọn Xoá cookie. Thao tác này sẽ báo cáo rằng cookie đã thực sự bị xoá.
- Chọn Xoá cookie một lần nữa. Thao tác này sẽ báo cáo rằng không có cookie nào để xoá.
- Chọn Liệt kê cookie. Thao tác này sẽ báo cáo rằng không có cookie nào.
- Chọn Thêm cookie. Thao tác này sẽ báo cáo cookie là đã được thêm.
- Chọn Đặt cookie. Thao tác này sẽ báo cáo cookie dưới dạng đã đặt.
- Chọn List cookies (Liệt kê cookie), sau đó chọn Remove cookie (Xoá cookie).
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 các tệp HTML bằng nhiều phương thức và hiển thị các tệp đó trong WebView. Trong bước này, bạn sẽ tải một tài sản Flutter được chỉ định trong tệp pubspec.yaml
, tải một tệp nằm ở đường dẫn đã chỉ định và tải một trang bằng Chuỗi HTML.
Nếu muốn tải một tệp nằm ở một đường dẫn cụ thể, bạn sẽ cần thêm path_provider
vào pubspec.yaml
. Đây là một trình bổ trợ Flutter để 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 này vào dự án, hãy làm theo các bước sau:
- Tạo một Thư mục mới có tên là
assets
trong thư mục gốc của dự án. - Tạo một Thư mục mới có tên là
www
trong thư mụcassets
. - Tạo một Thư mục mới có tên là
styles
trong thư mụcwww
. - Tạo một Tệp mới có tên
index.html
trong thư mụcwww
. - Tạo một Tệp mới có tên
style.css
trong thư mụcstyles
.
Sao chép và dán mã 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 dùng một vài dòng sau để đặt kiểu tiêu đề HTML:
assets/www/styles/style.css
h1 {
color: blue;
}
Giờ đây, khi 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 tài sản bạn vừa tạo, bạn chỉ cần gọi phương thức loadFlutterAsset
bằng WebViewController
và cung cấp đường dẫn đến tài sản làm tham số. Thêm phương thức sau vào cuối mã của bạn:
lib/src/menu.dart
Future<void> _onLoadFlutterAssetExample(
WebViewController controller, BuildContext context) async {
await controller.loadFlutterAsset('assets/www/index.html');
}
Tải tệp cục bộ
Để 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 chứa mã HTML. Bạn có thể thực hiện việc này bằng cách thêm mã HTML dưới dạng một chuỗi ở đầu mã trong tệp menu.dart
ngay bên dưới các nội dung 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 hai phương thức. _onLoadLocalFileExample
sẽ tải tệp bằng cách cung cấp đường dẫn dưới dạng một chuỗi do 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, bạn chỉ cần làm theo các bước đơn giản. WebViewController
có một phương thức mà bạn có thể sử dụng có tên 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ục trong trình đơn
Giờ đây, sau khi các thành phần được thiết lập và sẵn sàng sử dụng, đồng thời 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 lựa chọn trong trình đơn và liên kết các lựa chọn đó với 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.
],
);
}
Kiểm thử thành phần, tệp và chuỗi HTML
Để kiểm thử xem mã bạn vừa triển khai có hoạt động hay không, bạn có thể chạy mã trên thiết bị của mình và nhấp vào một trong các mục trình đơn mới được thêm. Lưu ý cách _onLoadFlutterAssetExample
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.
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 chỉnh 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ử các lớp học lập trình khác về Flutter.