1. Introduzione
Ultimo aggiornamento: 19/10/2021
Con il plug-in WebView Flutter puoi aggiungere un widget WebView alla tua app Flutter per Android o iOS. Su iOS, il widget WebView è supportato da WKWebView, mentre su Android è supportato da WebView. Il plug-in può eseguire il rendering dei widget Flutter sulla visualizzazione web. Ad esempio, è possibile visualizzare un menu a discesa sopra la visualizzazione web.
Cosa creerai
In questo codelab, creerai passo passo un'app mobile con una WebView utilizzando l'SDK Flutter. La tua app sarà in grado di:
- Visualizzare contenuti web in un
WebView - Visualizza i widget Flutter impilati sopra
WebView - Reagire agli eventi di avanzamento del caricamento della pagina
- Controlla
WebViewtramiteWebViewController - Bloccare i siti web utilizzando
NavigationDelegate - Valutare le espressioni JavaScript
- Gestire i callback da JavaScript con
JavascriptChannels - Impostare, rimuovere, aggiungere o mostrare i cookie
- Caricare e visualizzare HTML da asset, file o stringhe contenenti HTML
|
|
Cosa imparerai
In questo codelab imparerai a utilizzare il webview_flutter plug-in in vari modi, tra cui:
- Come configurare il plug-in
webview_flutter - Come rilevare gli eventi di avanzamento del caricamento della pagina
- Come controllare la navigazione nelle pagine
- Come comandare a
WebViewdi andare avanti e indietro nella cronologia - Come valutare JavaScript, incluso l'utilizzo dei risultati restituiti
- Come registrare i callback per chiamare il codice Dart da JavaScript
- Come gestire i cookie
- Come caricare e visualizzare pagine HTML da asset, file o stringhe contenenti HTML
Requisiti
- Android Studio 4.1 o versioni successive (per lo sviluppo Android)
- Xcode 12 o versioni successive (per lo sviluppo di iOS)
- SDK Flutter
- Un editor di codice, ad esempio Android Studio o Visual Studio Code.
2. Configura l'ambiente di sviluppo Flutter
Per completare questo lab, hai bisogno di due software: l'SDK Flutter e un editor.
Puoi eseguire il codelab utilizzando uno qualsiasi di questi dispositivi:
- Un dispositivo fisico Android o iOS connesso al computer e impostato sulla modalità sviluppatore.
- Il simulatore iOS (richiede l'installazione degli strumenti Xcode).
- L'emulatore Android (richiede la configurazione in Android Studio).
3. Per iniziare
Introduzione a Flutter
Esistono diversi modi per creare un nuovo progetto Flutter. Sia Android Studio che Visual Studio Code forniscono strumenti per questa attività. Segui le procedure collegate per creare un progetto oppure esegui i seguenti comandi in un terminale a riga di comando pratico.
$ 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.
Aggiungere il plug-in WebView Flutter come dipendenza
L'aggiunta di funzionalità aggiuntive a un'app Flutter si ottiene al meglio utilizzando i pacchetti Pub. In questo codelab aggiungerai il plug-in webview_flutter al tuo progetto. Esegui questi comandi nel terminale.
$ 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.
Se esamini il file pubspec.yaml, vedrai che ora contiene una riga nella sezione delle dipendenze per il plug-in webview_flutter.
Configurare minSDK Android
Per utilizzare il plug-in webview_flutter su Android, devi impostare minSDK su 20. Modifica il file android/app/build.gradle come segue:
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. Aggiunta del widget WebView all'app Flutter
In questo passaggio aggiungerai un WebView alla tua applicazione. Le WebView sono viste integrate ospitate e, in qualità di sviluppatore di app, puoi scegliere come ospitare queste viste integrate nella tua app. Su Android puoi scegliere tra display virtuali, l'impostazione predefinita per Android, e composizione ibrida. Tuttavia, iOS utilizza sempre la composizione ibrida.
Per una discussione approfondita sulle differenze tra display virtuali e composizione ibrida, leggi la documentazione su Hosting di visualizzazioni native di Android e iOS nella tua app Flutter con Platform Views .
Inserimento di una WebView sullo schermo
Sostituisci i contenuti di lib/main.dart come segue:
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,
),
);
}
}
Se lo esegui su iOS o Android, sul tuo dispositivo verrà visualizzata una WebView come finestra del browser a tutta pagina, il che significa che il browser viene visualizzato sul tuo dispositivo a schermo intero senza bordi o margini. Man mano che scorri, noterai parti della pagina che potrebbero sembrare un po' strane. Questo perché JavaScript è disattivato e il rendering corretto di flutter.dev richiede JavaScript.
Esecuzione dell'app
Esegui l'app Flutter in iOS o Android per visualizzare una WebView, che mostra il sito web flutter.dev. In alternativa, esegui l'app in un emulatore Android o in un simulatore iOS. Puoi sostituire l'URL iniziale di WebView con, ad esempio, il tuo sito web.
$ flutter run
Supponendo che sia in esecuzione il simulatore o l'emulatore appropriato o che sia collegato un dispositivo fisico, dopo aver compilato e distribuito l'app sul dispositivo, dovresti visualizzare un risultato simile al seguente:
|
|
5. In ascolto di eventi di caricamento pagina
Il widget WebView fornisce diversi eventi di avanzamento del caricamento della pagina, che la tua app può ascoltare. Durante il ciclo di caricamento della pagina WebView vengono attivati tre diversi eventi di caricamento della pagina: onPageStarted, onProgress e onPageFinished. In questo passaggio implementerai un indicatore di caricamento della pagina. Inoltre, dimostrerai di poter eseguire il rendering dei contenuti Flutter nell'area dei contenuti WebView.
Aggiungere eventi di caricamento pagina all'app
Crea un nuovo file di origine in lib/src/web_view_stack.dart e inserisci i seguenti contenuti:
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,
),
],
);
}
}
Questo codice ha racchiuso il widget WebView in un Stack, sovrapponendo in modo condizionale il WebView con un LinearProgressIndicator quando la percentuale di caricamento della pagina è inferiore al 100%. Poiché ciò comporta uno stato del programma che cambia nel tempo, hai memorizzato questo stato in una classe State associata a un StatefulWidget.
Per utilizzare questo nuovo widget WebViewStack, modifica lib/main.dart come segue:
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(),
);
}
}
Quando esegui l'app, a seconda delle condizioni di rete e se il browser ha memorizzato nella cache la pagina a cui stai navigando, vedrai un indicatore di caricamento della pagina sovrapposto all'area dei contenuti WebView.
6. Utilizzo di WebViewController
Accesso a WebViewController dal widget WebView
Il widget WebView consente il controllo programmatico con un WebViewController. Questo controller viene reso disponibile dopo la costruzione del widget WebView tramite un callback. La natura asincrona della disponibilità di questo controller lo rende un candidato ideale per la classe asincrona Completer<T> di Dart.
Aggiorna lib/src/web_view_stack.dart come segue:
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,
),
],
);
}
}
Il widget WebViewStack ora utilizza un controller creato nel widget circostante. In questo modo, il controller per WebViewWidget potrà essere condiviso con altre parti dell'app.
Creare controlli di navigazione
Avere un WebView funzionante è una cosa, ma poter navigare avanti e indietro nella cronologia delle pagine e ricaricare la pagina sarebbe un insieme utile di aggiunte. Fortunatamente, con un WebViewController puoi aggiungere questa funzionalità alla tua app.
Crea un nuovo file di origine in lib/src/navigation_controls.dart e inserisci quanto segue:
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();
},
),
],
);
}
}
Questo widget utilizza il WebViewController condiviso al momento della creazione per consentire all'utente di controllare WebView tramite una serie di IconButton.
Aggiunta di controlli di navigazione alla barra delle app
Con l'WebViewStack aggiornato e il nuovo NavigationControls in mano, è ora di mettere tutto insieme in un WebViewApp aggiornato. Qui costruiamo il WebViewController condiviso. Con WebViewApp vicino alla parte superiore dell'albero dei widget in questa app, è logico crearlo a questo livello.
Aggiorna il file lib/main.dart come segue:
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
);
}
}
L'esecuzione dell'app dovrebbe mostrare una pagina web con i controlli:
|
|
7. Monitorare la navigazione con NavigationDelegate
WebView fornisce alla tua app un NavigationDelegate, che le consente di monitorare e controllare la navigazione delle pagine del widget WebView. Quando una navigazione viene avviata da WebView,, ad esempio quando un utente fa clic su un link, viene chiamato NavigationDelegate. Il callback NavigationDelegate può essere utilizzato per controllare se WebView procede con la navigazione.
Registra un NavigationDelegate personalizzato
In questo passaggio, registrerai un callback NavigationDelegate per bloccare la navigazione su YouTube.com. Tieni presente che questa implementazione semplificata blocca anche i contenuti YouTube incorporati, che vengono visualizzati in varie pagine della documentazione dell'API Flutter.
Aggiorna lib/src/web_view_stack.dart come segue:
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,
),
],
);
}
}
Nel passaggio successivo, aggiungerai una voce di menu per attivare il test di NavigationDelegate utilizzando la classe WebViewController. Il lettore è invitato a migliorare la logica del callback per bloccare solo la navigazione a schermo intero su YouTube.com e consentire comunque i contenuti di YouTube incorporati nella documentazione dell'API.
8. Aggiunta di un pulsante di menu alla barra delle app
Nei passaggi successivi, creerai un pulsante del menu nel widget AppBar utilizzato per valutare JavaScript, richiamare i canali JavaScript e gestire i cookie. Nel complesso, un menu davvero utile.
Crea un nuovo file di origine in lib/src/menu.dart e inserisci quanto segue:
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'),
),
],
);
}
}
Quando l'utente seleziona l'opzione di menu Vai a YouTube, viene eseguito il metodo loadRequest di WebViewController. Questa navigazione verrà bloccata dal callback navigationDelegate che hai creato nel passaggio precedente.
Per aggiungere il menu alla schermata di WebViewApp, modifica lib/main.dart come segue:
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),
);
}
}
Esegui l'app e tocca la voce di menu Vai a YouTube. Dovresti visualizzare una notifica SnackBar che ti informa che il controller di navigazione ha bloccato la navigazione su YouTube.
|
|
9. Valutazione di JavaScript
WebViewController può valutare le espressioni JavaScript nel contesto della pagina corrente. Esistono due modi diversi per valutare JavaScript: per il codice JavaScript che non restituisce un valore, utilizza runJavaScript e per il codice JavaScript che restituisce un valore, utilizza runJavaScriptReturningResult.
Per attivare JavaScript, devi configurare WebViewController con la proprietà javaScriptMode impostata su JavascriptMode.unrestricted. Per impostazione predefinita, javascriptMode è impostato su JavascriptMode.disabled.
Aggiorna la classe _WebViewStackState aggiungendo l'impostazione javascriptMode come segue:
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,
),
],
);
}
}
Ora che WebViewWidget può eseguire JavaScript, puoi aggiungere un'opzione al menu per utilizzare il metodo runJavaScriptReturningResult.
Utilizzando l'editor o la tastiera, converti la classe Menu in StatefulWidget. Modifica lib/src/menu.dart in modo che corrisponda a quanto segue:
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.
],
);
}
}
Quando tocchi l'opzione di menu "Mostra user agent", il risultato dell'esecuzione dell'espressione JavaScript navigator.userAgent viene visualizzato in un Snackbar. Quando esegui l'app, potresti notare che la pagina Flutter.dev ha un aspetto diverso. Questo è il risultato dell'esecuzione con JavaScript attivato.
|
|
10. Utilizzo dei canali JavaScript
I canali JavaScript consentono alla tua app di registrare i gestori di callback nel contesto JavaScript di WebViewWidget che possono essere richiamati per trasmettere i valori al codice Dart dell'app. In questo passaggio registrerai un canale SnackBar che verrà chiamato con il risultato di un XMLHttpRequest.
Aggiorna la classe WebViewStack come segue:
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,
),
],
);
}
}
Per ogni canale JavaScript in Set, un oggetto canale viene reso disponibile nel contesto JavaScript come proprietà della finestra con lo stesso nome del canale JavaScript name. L'utilizzo di questo elemento dal contesto JavaScript comporta la chiamata di postMessage sul canale JavaScript per inviare un messaggio che viene passato al gestore di callback onMessageReceived di JavascriptChannel denominato.
Per utilizzare il canale JavaScript che hai aggiunto in precedenza, aggiungi un'altra voce di menu che esegue un XMLHttpRequest nel contesto JavaScript e restituisce i risultati utilizzando il canale JavaScript SnackBar.
Ora che WebViewWidgetconosce i nostri canali JavaScript,, aggiungerai un esempio per espandere ulteriormente l'app. Per farlo, aggiungi un PopupMenuItem extra alla classe Menu e aggiungi la funzionalità extra.
Aggiorna _MenuOptions con l'opzione di menu aggiuntiva, aggiungendo il valore di enumerazione javascriptChannel e un'implementazione alla classe Menu come segue:
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.
],
);
}
}
Questo codice JavaScript viene eseguito quando l'utente sceglie l'opzione di menu Esempio di canale 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();
Questo codice invia una richiesta GET a un'API per indirizzi IP pubblici, restituendo l'indirizzo IP del dispositivo. Questo risultato viene mostrato in un SnackBar richiamando postMessage sul SnackBar JavascriptChannel.
11. Gestione dei cookie
La tua app può gestire i cookie in WebView utilizzando la classe CookieManager. In questo passaggio, mostrerai un elenco di cookie, lo cancellerai, eliminerai i cookie e ne imposterai di nuovi. Aggiungi voci al _MenuOptions per ciascuno dei casi d'uso dei cookie nel seguente modo:
lib/src/menu.dart
enum _MenuOptions {
navigationDelegate,
userAgent,
javascriptChannel,
// Add from here ...
listCookies,
clearCookies,
addCookie,
setCookie,
removeCookie,
// ... to here.
}
Il resto delle modifiche in questo passaggio si concentra sulla classe Menu, inclusa la conversione della classe Menu da stateless a stateful. Questa modifica è importante perché Menu deve possedere CookieManager e lo stato modificabile nei widget stateless è una combinazione negativa.
Aggiungi CookieManager alla classe State risultante nel seguente modo:
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) {
// ...
La classe _MenuState conterrà il codice aggiunto in precedenza nella classe Menu, insieme a CookieManager appena aggiunto. Nella serie successiva di sezioni, aggiungerai funzioni di supporto a _MenuState che, a loro volta, verranno richiamate dalle voci di menu ancora da aggiungere.
Visualizzare un elenco di tutti i cookie
Utilizzerai JavaScript per ottenere un elenco di tutti i cookie. Per farlo, aggiungi un metodo helper alla fine della classe _MenuState, chiamato _onListCookies. Utilizzando il metodo runJavaScriptReturningResult, il metodo helper esegue document.cookie nel contesto JavaScript, restituendo un elenco di tutti i cookie.
Aggiungi quanto segue al corso _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.'),
),
);
}
Cancella tutti i cookie
Per cancellare tutti i cookie nella WebView, utilizza il metodo clearCookies della classe CookieManager. Il metodo restituisce un Future<bool> che si risolve in true se CookieManager ha cancellato i cookie e false se non c'erano cookie da cancellare.
Aggiungi quanto segue al corso _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),
),
);
}
Aggiungere un cookie
L'aggiunta di un cookie può essere eseguita richiamando JavaScript. L'API utilizzata per aggiungere un cookie a un documento JavaScript è documentata in dettaglio su MDN.
Aggiungi quanto segue al corso _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.'),
),
);
}
Impostazione di un cookie con CookieManager
I cookie possono essere impostati anche utilizzando CookieManager come segue.
Aggiungi quanto segue al corso _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.'),
),
);
}
Rimuovere un cookie
La rimozione di un cookie comporta l'aggiunta di un cookie con una data di scadenza impostata nel passato.
Aggiungi quanto segue al corso _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.'),
),
);
}
Aggiunta delle voci di menu di CookieManager
Non ti resta che aggiungere le opzioni di menu e collegarle ai metodi helper che hai appena aggiunto. Aggiorna la classe _MenuState come segue:
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.
],
);
}
Utilizzo di CookieManager
Per utilizzare tutte le funzionalità che hai appena aggiunto all'app, prova a seguire questi passaggi:
- Seleziona Elenca cookie. Dovrebbe elencare i cookie di Google Analytics impostati da flutter.dev.
- Seleziona Cancella cookie. Dovrebbe segnalare che i cookie sono stati effettivamente cancellati.
- Seleziona di nuovo Cancella cookie. Dovrebbe segnalare che non erano disponibili cookie da cancellare.
- Seleziona Elenca cookie. Dovrebbe segnalare che non sono presenti cookie.
- Seleziona Aggiungi cookie. Dovrebbe segnalare che il cookie è stato aggiunto.
- Seleziona Imposta cookie. Dovrebbe segnalare che il cookie è stato impostato.
- Seleziona Elenca cookie e, come tocco finale, seleziona Rimuovi cookie.
|
|
|
|
12. Caricare asset, file e stringhe HTML di Flutter in WebView
La tua app può caricare file HTML utilizzando metodi diversi e visualizzarli in WebView. In questo passaggio caricherai una risorsa Flutter specificata nel file pubspec.yaml, un file che si trova nel percorso specificato e una pagina utilizzando una stringa HTML.
Se vuoi caricare un file che si trova in un percorso specificato, devi aggiungere path_provider a pubspec.yaml. Questo è un plug-in Flutter per trovare le posizioni di uso comune sul file system.
Nella riga di comando, esegui questo comando:
$ flutter pub add path_provider
Per caricare l'asset, dobbiamo specificare il percorso dell'asset in pubspec.yaml. In pubspec.yaml aggiungi le seguenti righe:
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.
Per aggiungere gli asset al progetto:
- Crea una nuova directory con il nome
assetsnella cartella principale del progetto. - Crea una nuova directory con il nome
wwwnella cartellaassets. - Crea una nuova directory con il nome
stylesnella cartellawww. - Crea un nuovo file con il nome
index.htmlnella cartellawww. - Crea un nuovo file con il nome
style.cssnella cartellastyles.
Copia e incolla il seguente codice nel file index.html:
assets/www/index.html
<!DOCTYPE html>
<!-- Copyright 2013 The Flutter Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<html lang="en">
<head>
<title>Load file or HTML string example</title>
<link rel="stylesheet" href="styles/style.css" />
</head>
<body>
<h1>Local demo page</h1>
<p>
This is an example page used to demonstrate how to load a local file or HTML
string using the <a href="https://pub.dev/packages/webview_flutter">Flutter
webview</a> plugin.
</p>
</body>
</html>
Per style.css, utilizza le seguenti righe per impostare lo stile dell'intestazione HTML:
assets/www/styles/style.css
h1 {
color: blue;
}
Ora che gli asset sono impostati e pronti per l'uso, puoi implementare i metodi necessari per caricare e visualizzare asset, file o stringhe HTML di Flutter.
Caricare l'asset Flutter
Per caricare l'asset appena creato, devi solo chiamare il metodo loadFlutterAsset utilizzando WebViewController e fornire come parametro il percorso dell'asset. Aggiungi il seguente metodo alla fine del codice:
lib/src/menu.dart
Future<void> _onLoadFlutterAssetExample(
WebViewController controller, BuildContext context) async {
await controller.loadFlutterAsset('assets/www/index.html');
}
Carica file locale
Per caricare un file sul tuo dispositivo, puoi aggiungere un metodo che utilizzerà il metodo loadFile, di nuovo utilizzando WebViewController, che accetta un String contenente il percorso del file.
Devi prima creare un file contenente il codice HTML. Per farlo, aggiungi il codice HTML come stringa nella parte superiore del codice nel file menu.dart appena sotto le importazioni.
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.
Per creare un File e scrivere la stringa HTML nel file, aggiungerai due metodi. _onLoadLocalFileExample caricherà il file fornendo il percorso come stringa restituita dal metodo _prepareLocalFile(). Aggiungi i seguenti metodi al tuo codice:
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;
}
Carica stringa HTML
Visualizzare una pagina fornendo una stringa HTML è piuttosto semplice. WebViewController ha un metodo che puoi utilizzare chiamato loadHtmlString, in cui puoi fornire la stringa HTML come argomento. Il WebView mostrerà quindi la pagina HTML fornita. Aggiungi il seguente metodo al tuo codice:
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.
Aggiungere le voci di menu
Ora che gli asset sono impostati e pronti per l'uso e i metodi con tutte le funzionalità sono stati creati, il menu può essere aggiornato. Aggiungi le seguenti voci all'enumerazione _MenuOptions:
lib/src/menu.dart
enum _MenuOptions {
navigationDelegate,
userAgent,
javascriptChannel,
listCookies,
clearCookies,
addCookie,
setCookie,
removeCookie,
// Add from here ...
loadFlutterAsset,
loadLocalFile,
loadHtmlString,
// ... to here.
}
Ora che l'enumerazione è aggiornata, puoi aggiungere le opzioni di menu e collegarle ai metodi helper che hai appena aggiunto. Aggiorna la classe _MenuState come segue:
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.
],
);
}
Test degli asset, del file e della stringa HTML
Per verificare se il codice che hai appena implementato funziona, puoi eseguirlo sul tuo dispositivo e fare clic su una delle voci di menu appena aggiunte. Nota come _onLoadFlutterAssetExample utilizza style.css che abbiamo aggiunto per cambiare l'intestazione del file HTML in blu.
|
|
13. Operazione completata.
Complimenti!!! Hai completato il codelab. Puoi trovare il codice completato per questo codelab nel repository del codelab.
Per saperne di più, prova gli altri codelab di Flutter.















