1. บทนำ
อัปเดตล่าสุด: 19-10-2021
ปลั๊กอิน WebView Flutter ช่วยให้คุณเพิ่มวิดเจ็ต WebView ลงในแอป Flutter ของ Android หรือ iOS ได้ โดยใน iOS วิดเจ็ต WebView จะได้รับการสนับสนุนจาก WKWebView ส่วนใน Android วิดเจ็ต WebView จะได้รับการสนับสนุนจาก WebView ปลั๊กอินสามารถแสดงผลวิดเจ็ต Flutter เหนือ WebView ได้ เช่น คุณสามารถแสดงเมนูแบบเลื่อนลงเหนือ WebView ได้
สิ่งที่คุณจะสร้าง
ในโค้ดแล็บนี้ คุณจะได้สร้างแอปบนอุปกรณ์เคลื่อนที่ทีละขั้นตอนโดยมี WebView โดยใช้ Flutter SDK แอปของคุณจะทำสิ่งต่อไปนี้
- แสดงเนื้อหาเว็บใน
WebView
- แสดงวิดเจ็ต Flutter ซ้อนทับ
WebView
- ตอบสนองต่อเหตุการณ์ความคืบหน้าในการโหลดหน้าเว็บ
- ควบคุม
WebView
ผ่านWebViewController
- บล็อกเว็บไซต์โดยใช้
NavigationDelegate
- ประเมินนิพจน์ JavaScript
- จัดการการเรียกกลับจาก JavaScript ด้วย
JavascriptChannels
- ตั้งค่า นำออก เพิ่ม หรือแสดงคุกกี้
- โหลดและแสดง HTML จากชิ้นงาน ไฟล์ หรือสตริงที่มี HTML
สิ่งที่คุณจะได้เรียนรู้
ในโค้ดแล็บนี้ คุณจะได้เรียนรู้วิธีใช้ปลั๊กอิน webview_flutter
ในหลากหลายวิธี ซึ่งรวมถึง
- วิธีกำหนดค่า
webview_flutter
ปลั๊กอิน - วิธีรอเหตุการณ์ความคืบหน้าในการโหลดหน้าเว็บ
- วิธีควบคุมการไปยังส่วนต่างๆ ของหน้าเว็บ
- วิธีสั่งให้
WebView
ย้อนกลับและไปข้างหน้าในประวัติ - วิธีประเมิน JavaScript รวมถึงการใช้ผลลัพธ์ที่ส่งคืน
- วิธีลงทะเบียน Callback เพื่อเรียกใช้โค้ด Dart จาก JavaScript
- วิธีจัดการคุกกี้
- วิธีโหลดและแสดงหน้า HTML จากชิ้นงาน ไฟล์ หรือสตริงที่มี HTML
สิ่งที่คุณต้องมี
- Android Studio 4.1 ขึ้นไป (สำหรับการพัฒนา Android)
- Xcode 12 ขึ้นไป (สําหรับการพัฒนา iOS)
- Flutter SDK
- โปรแกรมแก้ไขโค้ด เช่น Android Studio หรือ Visual Studio Code
2. ตั้งค่าสภาพแวดล้อมในการพัฒนา Flutter
คุณต้องใช้ซอฟต์แวร์ 2 อย่างเพื่อทำแล็บนี้ให้เสร็จสมบูรณ์ ได้แก่ Flutter SDK และโปรแกรมแก้ไข
คุณเรียกใช้ Codelab ได้โดยใช้อุปกรณ์ต่อไปนี้
- อุปกรณ์ Android หรือ iOS จริงที่เชื่อมต่อกับคอมพิวเตอร์และตั้งค่าเป็นโหมดนักพัฒนาแอป
- โปรแกรมจำลอง iOS (ต้องติดตั้งเครื่องมือ Xcode)
- โปรแกรมจำลอง Android (ต้องตั้งค่าใน Android Studio)
3. เริ่มต้นใช้งาน
การเริ่มต้นใช้งาน Flutter
การสร้างโปรเจ็กต์ Flutter ใหม่ทำได้หลายวิธี ทั้ง Android Studio และ Visual Studio Code มีเครื่องมือสำหรับงานนี้ ทำตามขั้นตอนที่ลิงก์ไว้เพื่อสร้างโปรเจ็กต์ หรือเรียกใช้คำสั่งต่อไปนี้ในเทอร์มินัลบรรทัดคำสั่งที่สะดวก
$ flutter create --platforms=android,ios webview_in_flutter Creating project webview_in_flutter... Resolving dependencies in `webview_in_flutter`... Downloading packages... Got dependencies in `webview_in_flutter`. Wrote 74 files. All done! You can find general documentation for Flutter at: https://docs.flutter.dev/ Detailed API documentation is available at: https://api.flutter.dev/ If you prefer video documentation, consider: https://www.youtube.com/c/flutterdev In order to run your application, type: $ cd webview_in_flutter $ flutter run Your application code is in webview_in_flutter/lib/main.dart.
การเพิ่มปลั๊กอิน WebView Flutter เป็นการขึ้นต่อกัน
การเพิ่มความสามารถให้กับแอป Flutter ทำได้ดีที่สุดโดยใช้แพ็กเกจ Pub ใน Codelab นี้ คุณจะได้เพิ่มปลั๊กอิน webview_flutter
ลงในโปรเจ็กต์ เรียกใช้คำสั่งต่อไปนี้ในเทอร์มินัล
$ 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.
หากตรวจสอบ pubspec.yaml คุณจะเห็นบรรทัดในส่วนการขึ้นต่อกันสำหรับปลั๊กอิน webview_flutter
กำหนดค่า minSDK ของ Android
หากต้องการใช้ปลั๊กอิน webview_flutter
ใน Android คุณต้องตั้งค่า minSDK
เป็น 20
แก้ไขไฟล์ android/app/build.gradle
ดังนี้
android/app/build.gradle
android {
//...
defaultConfig {
applicationId = "com.example.webview_in_flutter"
minSdk = 20 // Modify this line
targetSdk = flutter.targetSdkVersion
versionCode = flutterVersionCode.toInteger()
versionName = flutterVersionName
}
4. การเพิ่มวิดเจ็ต WebView ลงในแอป Flutter
ในขั้นตอนนี้ คุณจะเพิ่ม WebView
ลงในแอปพลิเคชัน WebView เป็นมุมมองในตัวที่โฮสต์ไว้ และในฐานะนักพัฒนาแอป คุณสามารถเลือกวิธีโฮสต์มุมมองในตัวเหล่านี้ในแอปได้ ใน Android คุณสามารถเลือกได้ระหว่างจอแสดงผลเสมือน ซึ่งเป็นค่าเริ่มต้นสำหรับ Android และการจัดองค์ประกอบแบบไฮบริด อย่างไรก็ตาม iOS จะใช้การจัดองค์ประกอบแบบไฮบริดเสมอ
หากต้องการดูการอภิปรายในเชิงลึกเกี่ยวกับความแตกต่างระหว่างจอแสดงผลเสมือนและการคอมโพสิตแบบไฮบริด โปรดอ่านเอกสารประกอบเกี่ยวกับการโฮสต์มุมมอง Android และ iOS ดั้งเดิมในแอป Flutter ด้วยมุมมองแพลตฟอร์ม
วาง WebView บนหน้าจอ
แทนที่เนื้อหาของ lib/main.dart
ด้วยข้อมูลต่อไปนี้
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,
),
);
}
}
การเรียกใช้ใน iOS หรือ Android จะแสดง WebView เป็นหน้าต่างเบราว์เซอร์แบบเต็มขอบบนอุปกรณ์ ซึ่งหมายความว่าเบราว์เซอร์จะแสดงบนอุปกรณ์แบบเต็มหน้าจอโดยไม่มีเส้นขอบหรือขอบ ขณะเลื่อน คุณจะเห็นส่วนต่างๆ ของหน้าเว็บที่อาจดูแปลกไปบ้าง เนื่องจากปิดใช้ JavaScript และการแสดงผล flutter.dev อย่างถูกต้องต้องใช้ JavaScript
การเรียกใช้แอป
เรียกใช้แอป Flutter ใน iOS หรือ Android เพื่อดู WebView ซึ่งแสดงเว็บไซต์ flutter.dev หรือจะเรียกใช้แอปในโปรแกรมจำลอง Android หรือโปรแกรมจำลอง iOS ก็ได้ คุณสามารถแทนที่ URL ของ WebView เริ่มต้นด้วยเว็บไซต์ของคุณเองได้
$ flutter run
สมมติว่าคุณมีโปรแกรมจำลองที่เหมาะสมหรือโปรแกรมจำลองที่กำลังทำงานอยู่ หรือมีอุปกรณ์จริงที่เชื่อมต่ออยู่ หลังจากคอมไพล์และติดตั้งใช้งานแอปในอุปกรณ์แล้ว คุณควรเห็นสิ่งที่คล้ายกับต่อไปนี้
5. รอรับเหตุการณ์การโหลดหน้าเว็บ
WebView
วิดเจ็ตมีเหตุการณ์ความคืบหน้าในการโหลดหน้าเว็บหลายรายการที่แอปของคุณสามารถรับฟังได้ ในระหว่างWebView
วงจรการโหลดหน้าเว็บ จะมีเหตุการณ์การโหลดหน้าเว็บ 3 เหตุการณ์ที่แตกต่างกันซึ่งจะทริกเกอร์ ได้แก่ onPageStarted
, onProgress
และ onPageFinished
ในขั้นตอนนี้ คุณจะติดตั้งใช้งานตัวบ่งชี้การโหลดหน้าเว็บ นอกจากนี้ การดำเนินการนี้ยังแสดงให้เห็นว่าคุณสามารถแสดงเนื้อหา Flutter เหนือWebView
พื้นที่เนื้อหาได้ด้วย
การเพิ่มเหตุการณ์การโหลดหน้าเว็บลงในแอป
สร้างไฟล์แหล่งข้อมูลใหม่ที่ lib/src/web_view_stack.dart
และกรอกเนื้อหาต่อไปนี้
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,
),
],
);
}
}
โค้ดนี้ได้รวมวิดเจ็ต WebView
ไว้ใน Stack
โดยจะวางซ้อน WebView
ด้วย LinearProgressIndicator
แบบมีเงื่อนไขเมื่อเปอร์เซ็นต์การโหลดหน้าเว็บน้อยกว่า 100% เนื่องจากเกี่ยวข้องกับสถานะโปรแกรมที่เปลี่ยนแปลงไปตามเวลา คุณจึงจัดเก็บสถานะนี้ไว้ในState
คลาสที่เชื่อมโยงกับStatefulWidget
หากต้องการใช้WebViewStack
วิดเจ็ตlib/main.dart
ใหม่นี้ ให้แก้ไขlib/main.dart
ดังนี้
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(),
);
}
}
เมื่อเรียกใช้แอป คุณจะเห็นตัวบ่งชี้การโหลดหน้าเว็บซ้อนทับอยู่ด้านบนWebView
พื้นที่เนื้อหา ทั้งนี้ขึ้นอยู่กับสภาพเครือข่ายและเบราว์เซอร์ได้แคชหน้าเว็บที่คุณกำลังไปยังหรือไม่
6. การทำงานกับ WebViewController
การเข้าถึง WebViewController จากวิดเจ็ต WebView
WebView
วิดเจ็ตช่วยให้ควบคุมแบบเป็นโปรแกรมได้ด้วย WebViewController
โดยตัวควบคุมนี้จะพร้อมใช้งานหลังจากสร้างวิดเจ็ต WebView
ผ่านการเรียกกลับ ลักษณะการทำงานแบบไม่พร้อมกันของความพร้อมใช้งานของตัวควบคุมนี้ทำให้เป็นตัวเลือกที่เหมาะสมที่สุดสำหรับคลาส Completer<T>
แบบไม่พร้อมกันของ Dart
อัปเดต lib/src/web_view_stack.dart
ดังนี้
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,
),
],
);
}
}
ตอนนี้วิดเจ็ต WebViewStack
จะใช้ตัวควบคุมที่สร้างในวิดเจ็ตโดยรอบ ซึ่งจะช่วยให้แชร์ตัวควบคุมสำหรับ WebViewWidget
กับส่วนอื่นๆ ของแอปได้
สร้างตัวควบคุมการนำทาง
การมี WebView
ที่ใช้งานได้เป็นสิ่งหนึ่ง แต่การไปยังส่วนต่างๆ ย้อนกลับและไปข้างหน้าผ่านประวัติหน้าเว็บ รวมถึงการโหลดหน้าเว็บซ้ำจะเป็นชุดฟีเจอร์เพิ่มเติมที่มีประโยชน์ โชคดีที่ WebViewController
ช่วยให้คุณเพิ่มฟังก์ชันนี้ลงในแอปได้
สร้างไฟล์ต้นฉบับใหม่ที่ lib/src/navigation_controls.dart
และกรอกข้อมูลต่อไปนี้
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();
},
),
],
);
}
}
วิดเจ็ตนี้ใช้ WebViewController
ที่แชร์กับวิดเจ็ตในเวลาที่สร้างเพื่อช่วยให้ผู้ใช้ควบคุม WebView
ผ่านชุด IconButton
การเพิ่มตัวควบคุมการนำทางไปยัง AppBar
เมื่อมีWebViewStack
ที่อัปเดตแล้วและNavigationControls
ที่สร้างขึ้นใหม่ในมือ ตอนนี้ก็ถึงเวลาที่คุณจะนำทุกอย่างมารวมกันในWebViewApp
ที่อัปเดตแล้ว เราจะสร้างWebViewController
ที่แชร์กันตรงนี้ เมื่อมี WebViewApp
ใกล้ด้านบนของโครงสร้างวิดเจ็ตในแอปนี้ การสร้างที่ระดับนี้จึงสมเหตุสมผล
อัปเดตไฟล์ lib/main.dart
ดังนี้
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
);
}
}
การเรียกใช้แอปควรแสดงหน้าเว็บที่มีตัวควบคุมดังนี้
7. การติดตามการนำทางด้วย NavigationDelegate
WebView
จะให้ NavigationDelegate,
แก่แอปของคุณ ซึ่งจะช่วยให้แอปติดตามและควบคุมการไปยังหน้าต่างๆ ของวิดเจ็ต WebView
ได้ เมื่อWebView,
เริ่มการนำทาง เช่น เมื่อผู้ใช้คลิกลิงก์ ระบบจะเรียกใช้ NavigationDelegate
NavigationDelegate
สามารถใช้การเรียกกลับเพื่อควบคุมว่า WebView
จะดำเนินการนำทางต่อหรือไม่
ลงทะเบียน NavigationDelegate ที่กำหนดเอง
ในขั้นตอนนี้ คุณจะลงทะเบียนNavigationDelegate
การเรียกกลับเพื่อบล็อกการไปยัง YouTube.com โปรดทราบว่าการติดตั้งใช้งานแบบง่ายนี้จะบล็อกเนื้อหา YouTube ในหน้าเว็บด้วย ซึ่งจะปรากฏในหน้าเอกสารประกอบ API ของ Flutter ต่างๆ
อัปเดต lib/src/web_view_stack.dart
ดังนี้
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,
),
],
);
}
}
ในขั้นตอนถัดไป คุณจะเพิ่มรายการเมนูเพื่อเปิดใช้การทดสอบ NavigationDelegate
โดยใช้คลาส WebViewController
ผู้อ่านควรฝึกฝนการเพิ่มตรรกะของ Callback เพื่อบล็อกการไปยังหน้าแบบเต็มไปยัง YouTube.com เท่านั้น และยังคงอนุญาตเนื้อหา YouTube ในหน้าเว็บในเอกสาร API
8. การเพิ่มปุ่มเมนูลงใน AppBar
ในอีก 2-3 ขั้นตอนถัดไป คุณจะสร้างปุ่มเมนูในวิดเจ็ต AppBar
ที่ใช้ประเมิน JavaScript เรียกใช้แชแนล JavaScript และจัดการคุกกี้ โดยรวมแล้วเป็นเมนูที่มีประโยชน์จริงๆ
สร้างไฟล์ต้นฉบับใหม่ที่ lib/src/menu.dart
และกรอกข้อมูลต่อไปนี้
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'),
),
],
);
}
}
เมื่อผู้ใช้เลือกตัวเลือกเมนูไปที่ YouTube ระบบจะเรียกใช้เมธอด loadRequest
ของ WebViewController
navigationDelegate
Callback ที่คุณสร้างไว้ในขั้นตอนก่อนหน้าจะบล็อกการนำทางนี้
หากต้องการเพิ่มเมนูลงในหน้าจอของ WebViewApp
ให้แก้ไข lib/main.dart
ดังนี้
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),
);
}
}
เรียกใช้แอป แล้วแตะรายการในเมนูไปที่ YouTube คุณควรเห็น SnackBar ที่แจ้งให้ทราบว่า Navigation Controller บล็อกการไปยัง YouTube
9. การประเมิน JavaScript
WebViewController
สามารถประเมินนิพจน์ JavaScript ในบริบทของหน้าเว็บปัจจุบันได้ การประเมิน JavaScript มี 2 วิธี ได้แก่ สำหรับโค้ด JavaScript ที่ไม่แสดงค่า ให้ใช้ runJavaScript
และสำหรับโค้ด JavaScript ที่แสดงค่า ให้ใช้ runJavaScriptReturningResult
หากต้องการเปิดใช้ JavaScript คุณต้องกําหนดค่า WebViewController
โดยตั้งค่าพร็อพเพอร์ตี้ javaScriptMode
เป็น JavascriptMode.unrestricted
โดยค่าเริ่มต้น ระบบจะตั้งค่า javascriptMode
เป็น JavascriptMode.disabled
อัปเดต_WebViewStackState
คลาสโดยเพิ่มการตั้งค่า javascriptMode
ดังนี้
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,
),
],
);
}
}
ตอนนี้ WebViewWidget
เรียกใช้ JavaScript ได้แล้ว คุณจึงเพิ่มตัวเลือกในเมนูเพื่อใช้วิธี runJavaScriptReturningResult
ได้
แปลงคลาสเมนูเป็น StatefulWidget โดยใช้เอดิเตอร์หรือการทำงานของแป้นพิมพ์ แก้ไข lib/src/menu.dart
ให้ตรงกับรายการต่อไปนี้
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.
],
);
}
}
เมื่อแตะตัวเลือกเมนู "แสดง User-Agent" ผลลัพธ์ของการเรียกใช้ JavaScript Expression navigator.userAgent
จะแสดงใน Snackbar
เมื่อเรียกใช้แอป คุณอาจสังเกตเห็นว่าหน้า Flutter.dev ดูแตกต่างออกไป นี่คือผลลัพธ์ของการเรียกใช้โดยเปิดใช้ JavaScript
10. การทำงานกับช่อง JavaScript
ช่อง JavaScript ช่วยให้แอปของคุณลงทะเบียนตัวแฮนเดิลการเรียกกลับในบริบท JavaScript ของ WebViewWidget
ซึ่งเรียกใช้เพื่อส่งค่ากลับไปยังโค้ด Dart ของแอปได้ ในขั้นตอนนี้ คุณจะลงทะเบียนช่อง SnackBar
ที่จะเรียกใช้พร้อมผลลัพธ์ของ XMLHttpRequest
อัปเดตชั้นเรียน WebViewStack
ดังนี้
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,
),
],
);
}
}
สำหรับแต่ละแชแนล JavaScript ใน Set
จะมีการสร้างออบเจ็กต์แชแนลในบริบท JavaScript เป็นพร็อพเพอร์ตี้ของออบเจ็กต์หน้าต่างที่มีชื่อเดียวกับแชแนล JavaScript name
การใช้จากบริบท JavaScript เกี่ยวข้องกับการเรียก postMessage
ในช่อง JavaScript เพื่อส่งข้อความที่ส่งไปยังแฮนเดิลเลอร์การเรียกกลับ onMessageReceived
ของ JavascriptChannel
ที่มีชื่อ
หากต้องการใช้ช่อง JavaScript ที่คุณเพิ่มไว้ก่อนหน้านี้ ให้เพิ่มรายการเมนูอีกรายการหนึ่งซึ่งเรียกใช้ XMLHttpRequest
ในบริบท JavaScript และส่งผลลัพธ์กลับโดยใช้ช่อง JavaScript SnackBar
ตอนนี้ WebViewWidget
รู้จักช่อง JavaScript ของเรา,
แล้ว คุณจะเพิ่มตัวอย่างเพื่อขยายแอปเพิ่มเติม โดยทำได้ด้วยการเพิ่ม PopupMenuItem
พิเศษลงในคลาส Menu
และเพิ่มฟังก์ชันการทำงานพิเศษ
อัปเดต _MenuOptions
ด้วยตัวเลือกเมนูเพิ่มเติมโดยการเพิ่มค่าการแจงนับ javascriptChannel
และเพิ่มการติดตั้งใช้งานลงในคลาส Menu
ดังนี้
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 นี้เมื่อผู้ใช้เลือกตัวเลือกเมนูตัวอย่างช่อง 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();
โค้ดนี้จะส่งGET
คำขอไปยัง API ที่อยู่ IP สาธารณะ ซึ่งจะแสดงที่อยู่ IP ของอุปกรณ์ ผลลัพธ์นี้จะแสดงใน SnackBar
โดยการเรียกใช้ postMessage
ใน SnackBar
JavascriptChannel
11. การจัดการคุกกี้
แอปของคุณจัดการคุกกี้ใน WebView
ได้โดยใช้คลาส CookieManager
ในขั้นตอนนี้ คุณจะแสดงรายการคุกกี้ ล้างรายการคุกกี้ ลบคุกกี้ และตั้งค่าคุกกี้ใหม่ เพิ่มรายการใน _MenuOptions
สำหรับ Use Case ของคุกกี้แต่ละรายการดังนี้
lib/src/menu.dart
enum _MenuOptions {
navigationDelegate,
userAgent,
javascriptChannel,
// Add from here ...
listCookies,
clearCookies,
addCookie,
setCookie,
removeCookie,
// ... to here.
}
การเปลี่ยนแปลงอื่นๆ ในขั้นตอนนี้มุ่งเน้นไปที่คลาส Menu
รวมถึงการแปลงคลาส Menu
จากแบบไม่มีสถานะเป็นแบบมีสถานะ การเปลี่ยนแปลงนี้มีความสำคัญเนื่องจาก Menu
ต้องเป็นเจ้าของ CookieManager
และสถานะที่เปลี่ยนแปลงได้ในวิดเจ็ตแบบไม่มีสถานะเป็นการผสมผสานที่ไม่ดี
เพิ่ม CookieManager ลงในคลาส State ที่ได้ดังนี้
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) {
// ...
_MenuState
จะมีโค้ดที่เพิ่มไว้ก่อนหน้านี้ในคลาส Menu
พร้อมกับ CookieManager
ที่เพิ่มใหม่ ในชุดส่วนถัดไป คุณจะเพิ่มฟังก์ชันตัวช่วยลงใน _MenuState
ซึ่งจะเรียกใช้รายการเมนูที่ยังไม่ได้เพิ่ม
ดูรายการคุกกี้ทั้งหมด
คุณจะใช้ JavaScript เพื่อรับรายการคุกกี้ทั้งหมด โดยให้เพิ่มเมธอดตัวช่วยที่ส่วนท้ายของคลาส _MenuState
ชื่อ _onListCookies
เมื่อใช้เมธอด runJavaScriptReturningResult
เมธอดตัวช่วยจะเรียกใช้ document.cookie
ในบริบท JavaScript ซึ่งจะแสดงรายการคุกกี้ทั้งหมด
เพิ่มรายการต่อไปนี้ลงในคลาส _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.'),
),
);
}
ล้างคุกกี้ทั้งหมด
หากต้องการล้างคุกกี้ทั้งหมดใน WebView ให้ใช้วิธี clearCookies
ของคลาส CookieManager
เมธอดจะแสดง Future<bool>
ที่จะเปลี่ยนเป็น true
หาก CookieManager
ล้างคุกกี้ และ false
หากไม่มีคุกกี้ให้ล้าง
เพิ่มรายการต่อไปนี้ลงในคลาส _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),
),
);
}
เพิ่มคุกกี้
คุณเพิ่มคุกกี้ได้โดยเรียกใช้ JavaScript API ที่ใช้เพื่อเพิ่มคุกกี้ลงในเอกสาร JavaScript มีคำอธิบายโดยละเอียดใน MDN
เพิ่มรายการต่อไปนี้ลงในคลาส _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.'),
),
);
}
การตั้งค่าคุกกี้ด้วย CookieManager
นอกจากนี้ยังตั้งค่าคุกกี้ได้โดยใช้ CookieManager ดังนี้
เพิ่มรายการต่อไปนี้ลงในคลาส _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.'),
),
);
}
นำคุกกี้ออก
การนำคุกกี้ออกเกี่ยวข้องกับการเพิ่มคุกกี้โดยกำหนดวันที่หมดอายุเป็นวันในอดีต
เพิ่มรายการต่อไปนี้ลงในคลาส _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.'),
),
);
}
การเพิ่มรายการเมนู CookieManager
สิ่งที่คุณต้องทำคือเพิ่มตัวเลือกเมนูและเชื่อมโยงตัวเลือกเหล่านั้นกับเมธอดตัวช่วยที่คุณเพิ่งเพิ่ม อัปเดตชั้นเรียน _MenuState
ดังนี้
lib/src/menu.dart
class _MenuState extends State<Menu> {
final cookieManager = WebViewCookieManager();
@override
Widget build(BuildContext context) {
return PopupMenuButton<_MenuOptions>(
onSelected: (value) async {
switch (value) {
case _MenuOptions.navigationDelegate:
await widget.controller
.loadRequest(Uri.parse('https://youtube.com'));
case _MenuOptions.userAgent:
final userAgent = await widget.controller
.runJavaScriptReturningResult('navigator.userAgent');
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('$userAgent'),
));
case _MenuOptions.javascriptChannel:
await widget.controller.runJavaScript('''
var req = new XMLHttpRequest();
req.open('GET', "https://api.ipify.org/?format=json");
req.onload = function() {
if (req.status == 200) {
let response = JSON.parse(req.responseText);
SnackBar.postMessage("IP Address: " + response.ip);
} else {
SnackBar.postMessage("Error: " + req.status);
}
}
req.send();''');
case _MenuOptions.clearCookies: // Add from here
await _onClearCookies();
case _MenuOptions.listCookies:
await _onListCookies(widget.controller);
case _MenuOptions.addCookie:
await _onAddCookie(widget.controller);
case _MenuOptions.setCookie:
await _onSetCookie(widget.controller);
case _MenuOptions.removeCookie:
await _onRemoveCookie(widget.controller); // To here.
}
},
itemBuilder: (context) => [
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.navigationDelegate,
child: Text('Navigate to YouTube'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.userAgent,
child: Text('Show user-agent'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.javascriptChannel,
child: Text('Lookup IP Address'),
),
const PopupMenuItem<_MenuOptions>( // Add from here
value: _MenuOptions.clearCookies,
child: Text('Clear cookies'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.listCookies,
child: Text('List cookies'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.addCookie,
child: Text('Add cookie'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.setCookie,
child: Text('Set cookie'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.removeCookie,
child: Text('Remove cookie'),
), // To here.
],
);
}
การใช้ CookieManager
หากต้องการใช้ฟังก์ชันทั้งหมดที่คุณเพิ่งเพิ่มลงในแอป ให้ลองทำตามขั้นตอนต่อไปนี้
- เลือกแสดงรายการคุกกี้ โดยควรแสดงคุกกี้ Google Analytics ที่ flutter.dev ตั้งค่าไว้
- เลือกล้างคุกกี้ ซึ่งควรรายงานว่าได้ล้างคุกกี้แล้ว
- เลือกล้างคุกกี้อีกครั้ง โดยควรรายงานว่าไม่มีคุกกี้ที่ล้างได้
- เลือกแสดงรายการคุกกี้ ซึ่งควรรายงานว่าไม่มีคุกกี้
- เลือกเพิ่มคุกกี้ โดยควรรายงานว่าได้เพิ่มคุกกี้แล้ว
- เลือกตั้งค่าคุกกี้ โดยควรรายงานว่ามีการตั้งค่าคุกกี้แล้ว
- เลือกแสดงคุกกี้ แล้วเลือกนำคุกกี้ออก
12. โหลดชิ้นงาน ไฟล์ และสตริง HTML ของ Flutter ใน WebView
แอปสามารถโหลดไฟล์ HTML ได้โดยใช้วิธีการต่างๆ และแสดงไฟล์เหล่านั้นใน WebView ในขั้นตอนนี้ คุณจะโหลดชิ้นงาน Flutter ที่ระบุไว้ในไฟล์ pubspec.yaml
โหลดไฟล์ที่อยู่ในเส้นทางที่ระบุ และโหลดหน้าเว็บโดยใช้สตริง HTML
หากต้องการโหลดไฟล์ที่อยู่ในเส้นทางที่ระบุ คุณจะต้องเพิ่ม path_provider
ลงใน pubspec.yaml
นี่คือปลั๊กอิน Flutter สำหรับค้นหาตำแหน่งที่ใช้กันทั่วไปในระบบไฟล์
เรียกใช้คำสั่งต่อไปนี้ในบรรทัดคำสั่ง
$ flutter pub add path_provider
หากต้องการโหลดชิ้นงาน เราต้องระบุเส้นทางไปยังชิ้นงานใน pubspec.yaml
ใน pubspec.yaml
ให้เพิ่มบรรทัดต่อไปนี้
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.
หากต้องการเพิ่มชิ้นงานลงในโปรเจ็กต์ ให้ทำตามขั้นตอนต่อไปนี้
- สร้างไดเรกทอรีใหม่โดยใช้ชื่อ
assets
ในโฟลเดอร์รูทของโปรเจ็กต์ - สร้างไดเรกทอรีใหม่ชื่อ
www
ในโฟลเดอร์assets
- สร้างไดเรกทอรีใหม่ชื่อ
styles
ในโฟลเดอร์www
- สร้างไฟล์ใหม่ชื่อ
index.html
ในโฟลเดอร์www
- สร้างไฟล์ใหม่ชื่อ
style.css
ในโฟลเดอร์styles
คัดลอกและวางโค้ดต่อไปนี้ในไฟล์ 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>
สำหรับ style.css ให้ใช้ 2-3 บรรทัดต่อไปนี้เพื่อตั้งค่าสไตล์ส่วนหัว HTML
assets/www/styles/style.css
h1 {
color: blue;
}
เมื่อตั้งค่าชิ้นงานและพร้อมใช้งานแล้ว คุณจะติดตั้งใช้งานเมธอดที่จำเป็นสำหรับการโหลดและแสดงชิ้นงาน ไฟล์ หรือสตริง HTML ของ Flutter ได้
โหลดชิ้นงาน Flutter
หากต้องการโหลดชิ้นงานที่เพิ่งสร้าง สิ่งที่คุณต้องทำคือเรียกใช้เมธอด loadFlutterAsset
โดยใช้ WebViewController
และระบุเส้นทางไปยังชิ้นงานเป็นพารามิเตอร์ เพิ่มเมธอดต่อไปนี้ที่ส่วนท้ายของโค้ด
lib/src/menu.dart
Future<void> _onLoadFlutterAssetExample(
WebViewController controller, BuildContext context) async {
await controller.loadFlutterAsset('assets/www/index.html');
}
โหลดไฟล์ในเครื่อง
สำหรับการโหลดไฟล์ในอุปกรณ์ คุณสามารถเพิ่มเมธอดที่จะใช้เมธอด loadFile
ได้อีกครั้งโดยใช้ WebViewController
ซึ่งรับ String
ที่มีเส้นทางไปยังไฟล์
คุณต้องสร้างไฟล์ที่มีโค้ด HTML ก่อน โดยทำได้ด้วยการเพิ่มโค้ด HTML เป็นสตริงที่ด้านบนของโค้ดในไฟล์ menu.dart
ใต้การนำเข้า
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.
หากต้องการสร้าง File
และเขียนสตริง HTML ลงในไฟล์ คุณจะต้องเพิ่ม 2 วิธี _onLoadLocalFileExample
จะโหลดไฟล์โดยระบุเส้นทางเป็นสตริงซึ่งเมธอด _prepareLocalFile()
จะแสดงผล เพิ่มเมธอดต่อไปนี้ลงในโค้ด
lib/src/menu.dart
Future<void> _onLoadLocalFileExample(
WebViewController controller, BuildContext context) async {
final String pathToIndex = await _prepareLocalFile();
await controller.loadFile(pathToIndex);
}
static Future<String> _prepareLocalFile() async {
final String tmpDir = (await getTemporaryDirectory()).path;
final File indexFile = File('$tmpDir/www/index.html');
await Directory('$tmpDir/www').create(recursive: true);
await indexFile.writeAsString(kExamplePage);
return indexFile.path;
}
โหลดสตริง HTML
การแสดงหน้าเว็บโดยระบุสตริง HTML นั้นทำได้ง่ายๆ WebViewController
มีเมธอดที่คุณใช้ได้ชื่อ loadHtmlString
ซึ่งคุณสามารถระบุสตริง HTML เป็นอาร์กิวเมนต์ได้ จากนั้น WebView
จะแสดงหน้า HTML ที่ระบุ เพิ่มวิธีการต่อไปนี้ลงในโค้ด
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.
เพิ่มรายการในเมนู
เมื่อตั้งค่าชิ้นงานและพร้อมใช้งานแล้ว รวมถึงสร้างวิธีการที่มีฟังก์ชันทั้งหมดแล้ว ก็จะอัปเดตเมนูได้ เพิ่มรายการต่อไปนี้ลงใน _MenuOptions
enum
lib/src/menu.dart
enum _MenuOptions {
navigationDelegate,
userAgent,
javascriptChannel,
listCookies,
clearCookies,
addCookie,
setCookie,
removeCookie,
// Add from here ...
loadFlutterAsset,
loadLocalFile,
loadHtmlString,
// ... to here.
}
ตอนนี้คุณอัปเดต Enum แล้ว จึงเพิ่มตัวเลือกเมนูและเชื่อมโยงกับเมธอด Helper ที่เพิ่งเพิ่มได้ อัปเดตชั้นเรียน _MenuState
ดังนี้
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.
],
);
}
การทดสอบชิ้นงาน ไฟล์ และสตริง HTML
หากต้องการทดสอบว่าโค้ดที่คุณเพิ่งติดตั้งใช้งานทํางานหรือไม่ คุณสามารถเรียกใช้โค้ดในอุปกรณ์และคลิกรายการเมนูที่เพิ่งเพิ่มใหม่รายการใดรายการหนึ่ง โปรดสังเกตว่า _onLoadFlutterAssetExample
ใช้ style.css
ที่เราเพิ่มเพื่อเปลี่ยนส่วนหัวของไฟล์ HTML เป็นสีน้ำเงิน
13. เสร็จเรียบร้อย
ขอแสดงความยินดี! คุณทำ Codelab เสร็จแล้ว คุณดูโค้ดที่เสร็จสมบูรณ์แล้วสำหรับ Codelab นี้ได้ในที่เก็บ Codelab
ดูข้อมูลเพิ่มเติมได้ที่โค้ดแล็บ Flutter อื่นๆ