1. Introduzione
Ultimo aggiornamento: 2021-10-19
Con il plug-in Flutter di WebView 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ò visualizzare i widget Flutter nella vista web. Quindi, ad esempio, è possibile visualizzare un menu a discesa sulla vista web.
Cosa creerai
In questo codelab, creerai passo passo un'app mobile con un componente WebView utilizzando l'SDK Flutter. La tua app sarà in grado di:
- Visualizza i contenuti web in un
WebView
- Mostra widget Flutter sopra
WebView
- Reagisci agli eventi di avanzamento del caricamento pagina
- Controlla
WebView
tramiteWebViewController
- Blocca i siti web che utilizzano l'
NavigationDelegate
- Valutare le espressioni JavaScript
- Gestisci i callback da JavaScript con
JavascriptChannels
- Impostare, rimuovere, aggiungere o mostrare cookie
- Carica e visualizza HTML da asset, file o stringhe contenenti HTML
Cosa imparerai a fare
In questo codelab imparerai a utilizzare il plug-in webview_flutter
in vari modi, tra cui:
- Come configurare il plug-in
webview_flutter
- Come ascoltare gli eventi di avanzamento del caricamento pagina
- Come controllare la navigazione nelle pagine
- Come ordinare a
WebView
di 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 o file o una stringa contenente HTML
Che cosa ti serve
- Android Studio 4.1 o versioni successive (per lo sviluppo Android)
- Xcode 12 o versione successiva (per sviluppo iOS)
- SDK Flutter
- Un editor di codice, ad esempio Android Studio, Visual Studio Code o Emacs.
2. Configura l'ambiente di sviluppo di Flutter
Per completare questo lab sono necessari due software: l'SDK Flutter e l'editor.
Puoi eseguire il codelab utilizzando uno 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
Guida introduttiva a Flutter
Esistono diversi modi per creare un nuovo progetto Flutter. Gli strumenti disponibili per questa attività sono sia Android Studio sia Visual Studio Code. Segui le procedure collegate per creare un progetto oppure esegui i comandi riportati di seguito in un pratico terminale a riga di comando.
$ 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.
Aggiunta del plug-in Flutter WebView come dipendenza
È facile aggiungere ulteriori funzionalità a un'app Flutter utilizzando i pacchetti Pub. In questo codelab, aggiungerai il plug-in webview_flutter
al tuo progetto. Esegui i seguenti comandi nel terminale.
$ cd webview_in_flutter $ flutter pub add webview_flutter Resolving dependencies... Downloading packages... leak_tracker 10.0.4 (10.0.5 available) leak_tracker_flutter_testing 3.0.3 (3.0.5 available) material_color_utilities 0.8.0 (0.11.1 available) meta 1.12.0 (1.14.0 available) + plugin_platform_interface 2.1.8 test_api 0.7.0 (0.7.1 available) + webview_flutter 4.7.0 + webview_flutter_android 3.16.0 + webview_flutter_platform_interface 2.10.0 + webview_flutter_wkwebview 3.13.0 Changed 5 dependencies! 5 packages have newer versions incompatible with dependency constraints. Try `flutter pub outdated` for more information.
Se controlli il file pubspec.yaml, ora vedrai che include una riga nella sezione delle dipendenze per il plug-in webview_flutter
.
Configura Android minSDK
Per usare 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. I componenti WebView sono visualizzazioni native ospitate, e gli sviluppatori di app possono scegliere come ospitarle nella propria app. Su Android puoi scegliere tra Display virtuali, attualmente 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 sull'hosting delle viste Android e iOS native nell'app Flutter con le viste piattaforma.
Visualizzare un componente 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 esegui questa operazione su iOS o Android, sul dispositivo verrà visualizzata una WebView come finestra del browser a schermo intero, il che significa che il browser viene visualizzato sul tuo dispositivo a schermo intero senza alcun tipo di bordo o margine. Mentre scorri, noterai che alcune parti della pagina potrebbero avere un aspetto un po' strano. Il motivo è che JavaScript è attualmente disattivato e il rendering di flutter.dev richiede correttamente JavaScript.
Esecuzione dell'app
Esegui l'app Flutter su iOS o Android per visualizzare un componente WebView con il sito web flutter.dev. In alternativa, esegui l'app in un emulatore Android o in un simulatore iOS. Puoi sostituire l'URL WebView iniziale con, ad esempio, il tuo sito web.
$ flutter run
Supponendo che sia in esecuzione l'emulatore o il simulatore appropriato oppure sia collegato un dispositivo fisico, dopo aver compilato e implementato l'app sul tuo dispositivo, il risultato dovrebbe essere simile al seguente:
5. In ascolto degli eventi di caricamento pagina
Il widget WebView
fornisce diversi eventi di avanzamento del caricamento pagina, che la tua app può ascoltare. Durante il ciclo di caricamento della pagina di WebView
, vengono attivati tre diversi eventi di caricamento della pagina: onPageStarted
, onProgress
e onPageFinished
. In questo passaggio implementerai un indicatore di caricamento pagina. Inoltre, dimostra che puoi eseguire il rendering dei contenuti di Flutter nell'area dei contenuti WebView
.
Aggiungere eventi di caricamento pagina all'app
Crea un nuovo file di origine all'indirizzo lib/src/web_view_stack.dart
e compilalo con 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 aggregato il widget WebView
all'interno di un elemento Stack
, sovrapponendo in modo condizionale WebView
a LinearProgressIndicator
quando la percentuale di caricamento della pagina è inferiore al 100%. Poiché lo stato del programma cambia nel tempo, hai archiviato questo stato in una classe State
associata a StatefulWidget
.
Per utilizzare questo nuovo widget WebViewStack
, modifica il tuo file lib/main.ARROW 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 della rete e se il browser ha memorizzato nella cache la pagina visualizzata, vedrai un indicatore di caricamento della pagina sovrapposto all'area dei contenuti di 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 creazione del widget WebView
tramite un callback. La natura asincrona della disponibilità di questo controller lo rende un candidato ideale per la classe Completer<T>
asincrona di Dart.
Aggiorna lib/src/web_view_stack.dart
nel seguente modo:
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
verrà facilmente condiviso con altre parti dell'app.
Controlli di navigazione nella creazione di lavori
Avere un WebView
funzionante è una cosa, ma essere in grado di navigare avanti e indietro nella cronologia delle pagine e di ricaricarla sarebbe un utile insieme di aggiunte. Per fortuna, con un WebViewController
puoi aggiungere questa funzionalità alla tua app.
Crea un nuovo file di origine all'indirizzo lib/src/navigation_controls.dart
e compilalo con 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 usa il WebViewController
condiviso in fase di creazione per consentire all'utente di controllare WebView
tramite una serie di IconButton
.
Aggiunta dei controlli di navigazione ad AppBar
Con la nuova versione di WebViewStack
e il nuovo NavigationControls
in mano, è il momento di riassemblare tutto in un WebViewApp
aggiornato. È qui che costruiamo l'elemento WebViewController
condiviso. Con WebViewApp
in cima alla struttura ad albero dei widget in questa app, ha senso 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 dei controlli:
7. Tenere traccia della navigazione con NavigationDelega
WebView
fornisce alla tua app un NavigationDelegate,
, che consente all'app di monitorare e controllare la navigazione nelle pagine del widget WebView
. Quando WebView,
avvia una navigazione, ad esempio quando un utente fa clic su un link, viene chiamato NavigationDelegate
. È possibile utilizzare il callback NavigationDelegate
per controllare se WebView
procede con la navigazione.
Registrare un NavigationDelegato personalizzato
In questo passaggio registrerai un callback NavigationDelegate
per bloccare la navigazione su YouTube.com. Tieni presente che questa implementazione semplicistica blocca anche i contenuti in linea di YouTube, che sono disponibili 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 consentire di testare NavigationDelegate
usando la classe WebViewController
. Il lettore ha il compito di potenziare la logica del callback in modo da bloccare solo la navigazione dell'intera pagina su YouTube.com, pur consentendo l'incorporamento dei contenuti di YouTube nella documentazione dell'API.
8. Aggiunta di un pulsante di menu alla barra delle app
Nei prossimi passaggi, creerai un pulsante di menu nel widget AppBar
che verrà utilizzato per valutare JavaScript, richiamare canali JavaScript e gestire i cookie. Nel complesso, un menu davvero utile.
Crea un nuovo file di origine all'indirizzo lib/src/menu.dart
e compilalo con 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
. La navigazione verrà bloccata dal callback navigationDelegate
che hai creato nel passaggio precedente.
Per aggiungere il menu allo schermo di WebViewApp
, modifica lib/main.dart
in questo modo:
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 ricevere uno 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, utilizzare runJavaScript
e per il codice JavaScript che restituisce un valore, utilizzare runJavaScriptReturningResult
.
Per attivare JavaScript, devi configurare WebViewController
con la proprietà javaScriptMode
impostata su JavascriptMode.unrestricted
. Per impostazione predefinita, l'opzione javascriptMode
è impostata 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 l'WebViewWidget
può eseguire JavaScript, puoi aggiungere al menu un'opzione per utilizzare il metodo runJavaScriptReturningResult
.
Utilizzando l'Editor o alcune funzioni da tastiera, converti la classe Menu in uno 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 "Mostra user agent" , il risultato dell'esecuzione dell'espressione JavaScript navigator.userAgent
viene visualizzato in Snackbar
. Quando esegui l'app, potresti notare che la pagina di 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 gestori di callback nel contesto JavaScript di WebViewWidget
che possono essere richiamati per restituire i valori al codice Dart dell'app. In questo passaggio registrerai un canale SnackBar
che verrà chiamato con il risultato di un XMLHttpRequest
.
Aggiorna il corso 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à finestra denominata con lo stesso nome del canale JavaScript name
. L'utilizzo di questo codice dal contesto JavaScript comporta la chiamata a postMessage
sul canale JavaScript per l'invio di un messaggio che viene passato al gestore di callback onMessageReceived
denominato JavascriptChannel
.
Per utilizzare il canale JavaScript aggiunto in precedenza, aggiungi un'altra voce di menu che esegua XMLHttpRequest
nel contesto JavaScript e restituisce i risultati utilizzando il canale JavaScript SnackBar
.
Ora che WebViewWidget
conosce i nostri canali JavaScript,
aggiungi un esempio per espandere ulteriormente l'app. Per farlo, aggiungi un altro PopupMenuItem
alla classe Menu
e 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 Public IP Address, restituendo l'indirizzo IP del dispositivo. Questo risultato viene mostrato in SnackBar
richiamando postMessage
in JavascriptChannel
di SnackBar
.
11. Gestione dei cookie
La tua app può gestire i cookie in WebView
usando la classe CookieManager
. In questo passaggio, visualizzerai un elenco di cookie, cancellerai l'elenco di cookie, eliminerai i cookie e imposterai nuovi cookie. Aggiungi voci a _MenuOptions
per ciascuno dei casi d'uso dei cookie, come segue:
lib/src/menu.dart
enum _MenuOptions {
navigationDelegate,
userAgent,
javascriptChannel,
// Add from here ...
listCookies,
clearCookies,
addCookie,
setCookie,
removeCookie,
// ... to here.
}
Le altre modifiche apportate in questo passaggio sono incentrate sulla classe Menu
, inclusa la conversione della classe Menu
da stateless a stateful. Questa modifica è importante perché Menu
deve essere proprietario del CookieManager
e lo stato modificabile nei widget stateless è una combinazione errata.
Aggiungi il CookieManager alla classe State risultante come segue:
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) {
// ...
Il corso _MenuState
conterrà il codice aggiunto in precedenza alla classe Menu
, insieme al nuovo CookieManager
appena aggiunto. Nella prossima serie di sezioni, aggiungerai a _MenuState
funzioni helper che, a loro volta, verranno richiamate dalle voci di menu ancora da aggiungere.
Ottenere un elenco di tutti i cookie
Stai utilizzando JavaScript per ottenere un elenco di tutti i cookie. A questo scopo, aggiungi un metodo helper alla fine della lezione _MenuState
, chiamato _onListCookies
. Se utilizzi il metodo runJavaScriptReturningResult
, il tuo metodo helper esegue document.cookie
nel contesto JavaScript, restituendo un elenco di tutti i cookie.
Aggiungi quanto segue alla classe _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.'),
),
);
}
Cancellare tutti i cookie
Per cancellare tutti i cookie in 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 erano presenti cookie da cancellare.
Aggiungi quanto segue alla classe _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 effettuata richiamando JavaScript. L'API utilizzata per aggiungere un cookie a un documento JavaScript è documentata in dettaglio su MDN.
Aggiungi quanto segue alla classe _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.'),
),
);
}
Impostare un cookie con CookieManager
I cookie possono essere impostati anche con CookieManager come descritto di seguito.
Aggiungi quanto segue alla classe _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 alla classe _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.'),
),
);
}
Aggiungere le voci di menu di CookieManager
Devi solo aggiungere le opzioni di menu e collegarle ai metodi di supporto appena aggiunti. Aggiorna il corso _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.
],
);
}
Allenamento di CookieManager
Per utilizzare tutte le funzionalità che hai appena aggiunto all'app, prova a procedere nel seguente modo:
- 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. Deve indicare che non sono disponibili cookie da cancellare.
- Seleziona Elenca cookie. Deve segnalare che non sono presenti cookie.
- Seleziona Aggiungi cookie. Il cookie dovrebbe essere segnalato come aggiunto.
- Seleziona Imposta cookie. Il cookie dovrebbe essere segnalato come impostato.
- Seleziona Elenca cookie e, come ritocco finale, seleziona Rimuovi cookie.
12. Carica asset Flutter, file e stringhe HTML in WebView
La tua app può caricare file HTML utilizzando diversi metodi e visualizzarli in WebView. In questo passaggio caricherai un asset Flutter specificato nel file pubspec.yaml
, caricherai un file che si trova nel percorso specificato e caricherai una pagina utilizzando una stringa HTML.
Se vuoi caricare un file che si trova in un percorso specificato, devi aggiungere path_provider
al file pubspec.yaml
. Si tratta di un plug-in Flutter per trovare le posizioni di uso comune nel file system.
Nella riga di comando, esegui questo comando:
$ flutter pub add path_provider
Per caricare l'asset dobbiamo specificare il relativo percorso nel 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
assets
nella cartella principale del progetto. - Crea una nuova Directory con il nome
www
nella cartellaassets
. - Crea una nuova Directory con il nome
styles
nella cartellawww
. - Crea un nuovo file con il nome
index.html
nella cartellawww
. - Crea un nuovo file con il nome
style.css
nella cartellastyles
.
Copia e incolla il seguente codice nel file 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>
Utilizza le seguenti righe per impostare lo stile dell'intestazione HTML nel file 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 Flutter.
Carica asset Flutter
Per caricare l'asset appena creato, non devi fare altro che richiamare il metodo loadFlutterAsset
utilizzando WebViewController
e assegnare 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 dispositivo puoi aggiungere un metodo che utilizzerà il metodo loadFile
, sempre utilizzando WebViewController
che richiede un String
contenente il percorso del file.
Devi prima creare un file contenente il codice HTML. A tale scopo, aggiungi il codice HTML come stringa nella parte superiore del codice nel file menu.dart
immediatamente sotto le importazioni.
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, che viene restituita dal metodo _prepareLocalFile()
. Aggiungi i seguenti metodi al tuo codice:
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
offre un metodo utilizzabile denominato loadHtmlString
in cui è possibile assegnare la stringa HTML come argomento. WebView
mostrerà la pagina HTML fornita. Aggiungi il seguente metodo al codice:
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 del menu
Ora che gli asset sono impostati e pronti per l'uso e i metodi con tutte le funzionalità sono pronti, puoi aggiornare il menu. Aggiungi le seguenti voci all'enum _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'enum è stata aggiornata, puoi aggiungere le opzioni di menu e collegarle ai metodi helper appena aggiunti. Aggiorna il corso _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 di asset, file e stringa HTML
Per verificare se il codice funzionava dopo l'implementazione, esegui il codice sul tuo dispositivo e fai clic su una delle voci di menu appena aggiunte. Nota come _onLoadFlutterAssetExample
utilizza il style.css
che abbiamo aggiunto per modificare l'intestazione del file HTML nel colore blu.
13. Operazione completata.
Complimenti!!! Hai completato il codelab. Puoi trovare il codice completato per questo codelab nel repository codelab.
Per scoprire di più, prova gli altri codelab di Flutter.