1. Présentation
Dernière mise à jour : 19/10/2021
Le plug-in WebView de Flutter permet d'ajouter un widget WebView à votre application Flutter pour Android ou iOS. Sur iOS, le widget WebView repose sur WKWebView, tandis que sur Android, le widget WebView s'appuie sur une instance WebView. Le plug-in peut afficher des widgets Flutter par-dessus la vue Web. Par exemple, il est possible d'afficher un menu déroulant par-dessus la vue Web.
Ce que vous allez faire
Dans cet atelier de programmation par étapes, vous allez créer une application mobile intégrant une instance WebView avec le SDK Flutter. Cette appli pourra :
- Afficher le contenu Web dans
WebView
- Afficher les widgets Flutter empilés sur
WebView
- Réagir aux événements de progression du chargement des pages
- Contrôler
WebView
viaWebViewController
- Bloquer les sites Web à l'aide de
NavigationDelegate
- Évaluer des expressions JavaScript
- Gérer les rappels à partir de JavaScript avec
JavascriptChannels
- Définir, supprimer, ajouter ou afficher des cookies
- Charger et afficher le code HTML à partir d'éléments, de fichiers ou de chaînes contenant du code HTML
Points abordés
Dans cet atelier de programmation, vous allez apprendre à utiliser le plug-in webview_flutter
de différentes manières, y compris les suivantes :
- Configuration du plug-in
webview_flutter
- Détection des événements de progression du chargement des pages
- Contrôle de la navigation sur les pages
- Contrôle de
WebView
pour revenir en arrière et avancer dans l'historique - Évaluation JavaScript, y compris à l'aide des résultats renvoyés
- Enregistrement des rappels pour appeler le code Dart à partir de JavaScript
- Gestion des cookies
- Chargement et affichage des pages HTML à partir d'éléments, de fichiers ou de chaînes contenant du code HTML
Prérequis
- Android Studio 4.1 ou version ultérieure (pour le développement sur Android)
- Xcode 12 ou version ultérieure (pour le développement sur iOS)
- SDK Flutter
- Un éditeur de code tel qu'Android Studio, Visual Studio Code ou Emacs
2. Configurer votre environnement de développement Flutter
Pour cet atelier, vous avez besoin de deux logiciels : le SDK Flutter et un éditeur.
Vous pouvez exécuter l'atelier de programmation sur l'un des appareils suivants :
- Un appareil Android ou iOS physique connecté à votre ordinateur et réglé en mode développeur.
- Le simulateur iOS (outils Xcode à installer).
- L'émulateur Android (qui doit être configuré dans Android Studio)
3. Premiers pas
Premiers pas avec Flutter
Vous pouvez créer un projet Flutter de plusieurs façons, en utilisant les outils fournis par Android Studio et Visual Studio Code pour cette tâche. Suivez les procédures associées pour créer un projet, ou exécutez les commandes suivantes dans un terminal de ligne de commande pratique.
$ flutter create --platforms=android,ios webview_in_flutter Creating project webview_in_flutter... Running "flutter pub get" in webview_in_flutter... 1,728ms Wrote 73 files. All done! In order to run your application, type: $ cd webview_in_flutter $ flutter run Your application code is in webview_in_flutter/lib/main.dart.
Ajouter le plug-in WebView de Flutter en tant que dépendance
Il est facile d'ajouter des fonctionnalités supplémentaires à une application Flutter à l'aide de packages Pub. Dans cet atelier de programmation, vous allez ajouter le plug-in webview_flutter
à votre projet. Exécutez les commandes suivantes dans le terminal.
$ cd webview_in_flutter $ flutter pub add webview_flutter
Si vous inspectez le fichier pubspec.yaml, vous constaterez qu'il comporte désormais une ligne dans la section des dépendances du plug-in webview_flutter
.
Configurer la version minimale (minSDK) d'Android
Pour utiliser le plug-in webview_flutter
sur Android, vous devez définir minSDK
sur 20
. Modifiez votre fichier android/app/build.gradle
comme suit :
android/app/build.gradle
android {
//...
defaultConfig {
applicationId "com.example.webview_in_flutter"
minSdkVersion 20 // MODIFY
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
4. Ajouter le widget WebView à l'application Flutter
À cette étape, vous allez ajouter WebView
à votre application. Les WebViews sont des vues natives hébergées, et en tant que développeur d'applications, vous pouvez choisir comment les héberger dans votre application. Sur Android, vous pouvez opter pour des affichages virtuels, actuellement définis par défaut pour Android et pour le mode mixte. Toutefois, iOS utilise toujours le mode mixte.
Pour une présentation détaillée des différences entre les écrans virtuels et le mode mixte, consultez la documentation sur l'hébergement des vues natives Android et iOS dans votre application Flutter avec les vues de la plate-forme.
Afficher une instance WebView à l'écran
Remplacez le contenu du bloc lib/main.dart
par le code suivant :
lib/main.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
void main() {
runApp(
const MaterialApp(
theme: ThemeData(useMaterial3: true),
home: 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,
),
);
}
}
Exécuter cette commande sur iOS ou Android affiche une instance WebView dans une fenêtre de navigateur à fond perdu sur votre appareil. Le navigateur s'affiche donc en plein écran sur votre appareil, sans aucune forme de bordure ni de marge. En faisant défiler la page, vous remarquerez peut-être que certaines parties de celle-ci peuvent sembler un peu bizarres. En effet, JavaScript est actuellement désactivé alors qu'il est nécessaire pour afficher flutter.dev correctement.
Exécuter l'application
Exécutez l'application Flutter sur iOS ou Android pour afficher une vue de la plate-forme qui comporte le site Web flutter.dev. Vous pouvez également exécuter l'application dans un émulateur Android ou un simulateur iOS. N'hésitez pas à remplacer l'URL WebView initiale par votre site Web, par exemple.
$ flutter run
Si vous avez installé le simulateur ou l'émulateur approprié ou si vous avez connecté un appareil physique, vous devriez obtenir ce qui suit après avoir compilé et déployé l'application :
5. Détecter les événements de chargement de pages
Grâce au widget WebView
, votre application peut détecter plusieurs événements de progression du chargement de la page. Au cours du cycle de chargement de la page WebView
, trois événements de chargement de page différents se déclenchent : onPageStarted
, onProgress
et onPageFinished
. À cette étape, vous allez implémenter un indicateur de chargement de page. En bonus, vous pouvez afficher le contenu Flutter dans la zone de contenu WebView
.
Ajouter des événements de chargement de pages à votre application
Créez un fichier source à l'adresse lib/src/web_view_stack.dart
et renseignez-le comme suit :
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,
),
],
);
}
}
Ce code a encapsulé le widget WebView
dans Stack
, superposant de manière conditionnelle WebView
avec LinearProgressIndicator
lorsque le pourcentage de chargement de la page est de moins de 100 %. Puisque cet état de programme change au fil du temps, vous l'avez stocké dans une classe State
associée à StatefulWidget
.
Pour utiliser ce nouveau widget WebViewStack
, modifiez le fichier lib/main.dart comme suit :
lib/main.dart
import 'package:flutter/material.dart';
import 'src/web_view_stack.dart';
void main() {
runApp(
const MaterialApp(
theme: ThemeData(useMaterial3: true),
home: 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(),
);
}
}
Lorsque vous exécutez l'application, suivant l'état de votre réseau et si le navigateur a mis en cache la page que vous consultez, un indicateur de chargement de page s'affiche en superposition sur la zone de contenu WebView
.
6. Utiliser WebViewController
Accéder à WebViewController à partir du widget WebView
Le widget WebView
permet un contrôle programmatique à l'aide de WebViewController
. Ce contrôleur est mis à disposition après la construction du widget WebView
via un rappel. Le caractère asynchrone de ce contrôleur en fait un candidat idéal pour la classe asynchrone Completer<T>
de Dart.
Mettez à jour lib/src/web_view_stack.dart
comme indiqué ci-dessous :
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,
),
],
);
}
}
Le widget WebViewStack
utilise maintenant un contrôleur créé dans le widget où il se trouve. Cela permet de partager facilement le contrôleur de WebViewWidget
avec d'autres parties de l'application.
Créer des commandes de navigation
Disposer d'un WebView
fonctionnel est une chose, mais il pourrait également être utile de pouvoir parcourir les pages ou l'historique, de revenir en arrière et d'actualiser la page. Heureusement, avec WebViewController
, vous pouvez ajouter cette fonctionnalité à votre application.
Créez un fichier source à l'adresse lib/src/navigation_controls.dart
et renseignez-le comme suit :
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();
},
),
],
);
}
}
Ce widget utilise WebViewController
, qui a été partagé avec lui lors de sa création pour permettre aux utilisateurs de contrôler WebView
avec une série de IconButton
.
Ajouter des commandes de navigation à AppBar
Il est temps d'assembler WebViewStack
qui vient d'être mis à jour et NavigationControls
qui vient d'être créé dans une nouvelle version de WebViewApp
. C'est à cet endroit que nous avons créé l'instance WebViewController
partagée. Avec WebViewApp
dans la partie supérieure de l'arborescence des widgets de cette application, il est judicieux d'effectuer la création à ce niveau.
Mettez à jour le fichier lib/main.dart
comme suit :
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(
const MaterialApp(
theme: ThemeData(useMaterial3: true),
home: 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
);
}
}
En exécutant l'application, vous devriez voir une page Web contenant les commandes :
7. Suivre la navigation avec la fonctionnalité NavigationDelegate
WebView
fournit NavigationDelegate,
à votre application pour lui permettre de suivre et de contrôler la navigation sur les pages du widget WebView
. Quand une navigation est initiée par WebView,
, par exemple lorsqu'un utilisateur clique sur un lien, NavigationDelegate
est appelé. Le rappel NavigationDelegate
peut être utilisé pour contrôler si WebView
poursuit la navigation.
Enregistrer une version personnalisée de NavigationDelegate
À cette étape, vous allez enregistrer un rappel NavigationDelegate
afin de bloquer la navigation vers YouTube.com. Notez que cette implémentation simplifiée bloque également le contenu YouTube intégré, qui apparaît dans différentes pages de documentation de l'API Flutter.
Mettez à jour lib/src/web_view_stack.dart
comme suit :
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,
),
],
);
}
}
À l'étape suivante, vous allez ajouter un élément de menu pour activer le test de votre NavigationDelegate
à l'aide de la classe WebViewController
. Il revient au lecteur de développer la logique du rappel afin de ne bloquer que la navigation en pleine page sur YouTube.com et de continuer à autoriser le contenu YouTube intégré dans la documentation de l'API.
8. Ajouter un bouton de menu à AppBar
Lors des prochaines étapes, vous allez créer un bouton de menu dans le widget AppBar
, qui permet d'évaluer JavaScript, d'appeler des canaux JavaScript et de gérer les cookies. Dans l'ensemble, un menu bien utile.
Créez un fichier source à l'adresse lib/src/menu.dart
et renseignez-le comme suit :
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'));
break;
}
},
itemBuilder: (context) => [
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.navigationDelegate,
child: Text('Navigate to YouTube'),
),
],
);
}
}
Quand l'utilisateur sélectionne l'option de menu Accéder à YouTube, la méthode loadRequest
de WebViewController
s'exécute. Cette navigation va être bloquée par le rappel navigationDelegate
que vous avez créé à l'étape précédente.
Pour ajouter le menu à l'écran de WebViewApp
, modifiez lib/main.dart
comme suit :
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(
const MaterialApp(
theme: ThemeData(useMaterial3: true),
home: 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),
);
}
}
Exécutez votre application et appuyez sur l'élément de menu Accéder à YouTube. Vous devriez recevoir un message SnackBar vous informant que la manette de navigation a bloqué la navigation sur YouTube.
9. Évaluer JavaScript
WebViewController
peut évaluer des expressions JavaScript dans le contexte de la page actuelle. Pour évaluer JavaScript, vous avez le choix entre deux méthodes : pour le code JavaScript qui ne renvoie pas de valeur, utilisez runJavaScript
et pour le code JavaScript renvoyant une valeur, utilisez runJavaScriptReturningResult
.
Pour activer JavaScript, vous devez configurer WebViewController
avec la propriété javaScriptMode
définie sur JavascriptMode.unrestricted
. Par défaut, javascriptMode
est défini sur JavascriptMode.disabled
.
Mettez à jour la classe _WebViewStackState
en ajoutant le paramètre javascriptMode
comme suit :
lib/src/web_view_stack.dart
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;
},
),
)
..setJavaScriptMode(JavaScriptMode.unrestricted);
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
WebViewWidget(
controller: widget.controller,
),
if (loadingPercentage < 100)
LinearProgressIndicator(
value: loadingPercentage / 100.0,
),
],
);
}
}
Maintenant que WebViewWidget
peut exécuter JavaScript, vous pouvez ajouter une option au menu pour utiliser la méthode runJavaScriptReturningResult
.
À l'aide de votre éditeur ou d'un raccourci clavier, convertissez la classe Menu en StatefulWidget. Modifiez lib/src/menu.dart
comme suit :
lib/src/menu.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
enum _MenuOptions {
navigationDelegate,
userAgent,
}
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'));
break;
case _MenuOptions.userAgent:
final userAgent = await widget.controller
.runJavaScriptReturningResult('navigator.userAgent');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('$userAgent'),
));
break;
}
},
itemBuilder: (context) => [
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.navigationDelegate,
child: Text('Navigate to YouTube'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.userAgent,
child: Text('Show user-agent'),
),
],
);
}
}
Lorsque vous appuyez sur l'option de menu "Afficher le user-agent", le résultat de l'exécution de l'expression JavaScript navigator.userAgent
est affiché dans Snackbar
. Lors de l'exécution de l'application, vous remarquerez peut-être que la page Flutter.dev s'affiche différemment. C'est le résultat de l'exécution avec JavaScript activé.
10. Utiliser les canaux JavaScript
Les canaux JavaScript permettent à votre application d'enregistrer des gestionnaires de rappel dans le contexte JavaScript de WebViewWidget
, qui peuvent être appelés pour transmettre les valeurs au code Dart de l'application. À cette étape, vous allez enregistrer un canal SnackBar
qui sera appelé avec le résultat de XMLHttpRequest
.
Mettez à jour la classe WebViewStack
comme suit :
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,
),
],
);
}
}
Pour chaque canal JavaScript dans Set
, un objet de canal est disponible dans le contexte JavaScript en tant que propriété de fenêtre portant le même nom que le canal JavaScript name
. L'utilisation de ce code depuis le contexte JavaScript implique d'appeler postMessage
sur le canal JavaScript pour envoyer un message transmis au gestionnaire de rappel onMessageReceived
nommé JavascriptChannel
.
Pour utiliser le canal JavaScript ajouté ci-dessous, spécifiez un autre élément de menu qui exécute XMLHttpRequest
dans le contexte JavaScript et renvoie les résultats à l'aide du canal JavaScript SnackBar
.
Maintenant que WebViewWidget
connaît notre canal JavaScript,
vous pouvez ajouter un exemple pour étendre l'application. Pour cela, ajoutez une autre instance de PopupMenuItem
à la classe Menu
, puis ajoutez les fonctionnalités supplémentaires.
Mettez à jour _MenuOptions
avec l'option de menu supplémentaire, en ajoutant la valeur d'énumération javascriptChannel
et en ajoutant une implémentation à la classe Menu
comme suit :
lib/src/menu.dart
enum _MenuOptions {
navigationDelegate,
userAgent,
javascriptChannel,
}
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'));
break;
case _MenuOptions.userAgent:
final userAgent = await widget.controller
.runJavaScriptReturningResult('navigator.userAgent');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('$userAgent'),
));
break;
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();''');
break;
}
},
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'),
),
],
);
}
}
Ce code JavaScript s'exécute lorsque l'utilisateur choisit l'option de menu Exemple de canal 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();
Ce code envoie une requête GET
à une API d'adresse IP publique, en renvoyant l'adresse IP de l'appareil. Ce résultat s'affiche dans SnackBar
en appelant postMessage
sur le SnackBar
JavascriptChannel
.
11. Gérer les cookies
Votre application peut gérer les cookies dans WebView
avec la classe CookieManager
. À cette étape, vous allez afficher une liste de cookies, l'effacer, supprimer les cookies et en définir de nouveaux. Ajoutez des entrées à _MenuOptions
pour chacun des cas d'utilisation de cookies comme suit :
lib/src/menu.dart
enum _MenuOptions {
navigationDelegate,
userAgent,
javascriptChannel,
// Add from here ...
listCookies,
clearCookies,
addCookie,
setCookie,
removeCookie,
// ... to here.
}
Les autres modifications de cette étape sont axées sur la classe Menu
, y compris la conversion de la classe Menu
de "sans état" à "avec état". Cette modification est importante, car Menu
doit posséder CookieManager
, et l'état modifiable dans les widgets sans état est une mauvaise combinaison.
Ajoutez CookieManager à la classe State obtenue, comme suit :
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
contient le code précédemment ajouté dans la classe Menu
, ainsi que la classe CookieManager
que vous venez d'ajouter. Dans les sections suivantes, vous ajouterez des fonctions d'assistance à _MenuState
, qui seront ensuite appelées par les éléments de menu à ajouter.
Obtenir la liste de tous les cookies
Vous allez utiliser JavaScript pour obtenir la liste de tous les cookies. Pour ce faire, ajoutez une méthode d'assistance à la fin de la classe _MenuState
, appelée _onListCookies
. À l'aide de la méthode runJavaScriptReturningResult
, votre méthode d'assistance exécute document.cookie
dans le contexte JavaScript et renvoie une liste de tous les cookies.
Ajoutez les éléments suivants à la 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.'),
),
);
}
Effacer tous les cookies
Pour effacer tous les cookies dans l'instance WebView, utilisez la méthode clearCookies
de la classe CookieManager
. La méthode renvoie Future<bool>
qui renvoie vers true
si CookieManager
a effacé les cookies et false
si aucun cookie n'a été supprimé.
Ajoutez les éléments suivants à la 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),
),
);
}
Ajouter un cookie
Vous pouvez ajouter un cookie en appelant JavaScript. L'API qui permet d'ajouter un cookie à un document JavaScript est décrite en détail sur MDN.
Ajoutez les éléments suivants à la 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.'),
),
);
}
Définir un cookie à l'aide de CookieManager
Vous pouvez également définir les cookies à l'aide de CookieManager comme suit.
Ajoutez les éléments suivants à la 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.'),
),
);
}
Supprimer un cookie
Pour supprimer un cookie, vous devez ajouter un cookie avec une date d'expiration déjà passée.
Ajoutez les éléments suivants à la 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.'),
),
);
}
Ajouter les éléments du menu CookieManager
Il vous suffit d'ajouter les options de menu et de les lier aux méthodes d'assistance que vous venez d'ajouter. Mettez à jour la classe _MenuState
comme suit :
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'));
break;
case _MenuOptions.userAgent:
final userAgent = await widget.controller
.runJavaScriptReturningResult('navigator.userAgent');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('$userAgent'),
));
break;
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();''');
break;
case _MenuOptions.clearCookies:
await _onClearCookies();
break;
case _MenuOptions.listCookies:
await _onListCookies(widget.controller);
break;
case _MenuOptions.addCookie:
await _onAddCookie(widget.controller);
break;
case _MenuOptions.setCookie:
await _onSetCookie(widget.controller);
break;
case _MenuOptions.removeCookie:
await _onRemoveCookie(widget.controller);
break;
}
},
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'),
),
],
);
}
Exécuter CookieManager
Pour utiliser toutes les fonctionnalités que vous venez d'ajouter à l'application, procédez comme suit :
- Sélectionnez Répertorier les cookies. Cette option doit répertorier les cookies Google Analytics définis par flutter.dev.
- Sélectionnez Effacer les cookies. Cette option doit indiquer que les cookies ont bien été supprimés.
- Sélectionnez à nouveau Effacer les cookies. Cette option doit indiquer qu'il n'y a aucun cookie à supprimer.
- Sélectionnez Répertorier les cookies. Cette option doit indiquer qu'il n'y a pas de cookie.
- Sélectionnez Ajouter un cookie. Cette option doit indiquer que le cookie a été ajouté.
- Sélectionnez Définir des cookies. Cette option doit indiquer que le cookie a été défini.
- Sélectionnez Répertorier les cookies, puis, en dernier lieu, sélectionnez Supprimer les cookies.
12. Charger des éléments, des fichiers et des chaînes HTML Flutter dans WebView
Votre application peut charger des fichiers HTML à l'aide de différentes méthodes et les afficher dans l'instance WebView. À cette étape, vous allez charger un élément Flutter spécifié dans le fichier pubspec.yaml
, charger un fichier situé au chemin spécifié et charger une page à l'aide d'une chaîne HTML.
Si vous souhaitez charger un fichier situé à un chemin spécifié, vous devez ajouter path_provider
à pubspec.yaml
. Il s'agit d'un plug-in Flutter permettant de trouver des emplacements couramment utilisés dans le système de fichiers.
Dans la ligne de commande, exécutez la commande suivante :
$ flutter pub add path_provider
Pour charger l'élément, vous devez spécifier son chemin dans pubspec.yaml
. Dans pubspec.yaml
, ajoutez les lignes suivantes :
pubspec.yaml.
# The following section is specific to Flutter.
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.
Pour ajouter les éléments à votre projet procédez comme suit :
- Créez un répertoire nommé
assets
dans le dossier racine de votre projet. - Créez un répertoire nommé
www
dans le dossierassets
. - Créez un répertoire nommé
styles
dans le dossierwww
. - Créez un fichier nommé
index.html
dans le dossierwww
. - Créez un fichier nommé
style.css
dans le dossierstyles
.
Copiez et collez le code suivant dans le fichier 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>
Pour définir le style d'en-tête HTML, utilisez le code suivant pour les styles.css :
h1 {
color: blue;
}
Maintenant que les éléments sont définis et prêts à être utilisés, vous pouvez mettre en œuvre les méthodes nécessaires pour charger et afficher les éléments, les fichiers ou les chaînes HTML Flutter.
Charger l'élément Flutter
Pour charger l'élément que vous venez de créer, il vous suffit d'appeler la méthode loadFlutterAsset
à l'aide de WebViewController
et d'indiquer le chemin d'accès à l'élément en tant que paramètre. Ajoutez la méthode suivante à la fin de votre code :
lib/src/menu.dart
Future<void> _onLoadFlutterAssetExample(
WebViewController controller, BuildContext context) async {
await controller.loadFlutterAsset('assets/www/index.html');
}
Charger un fichier local
Pour charger un fichier sur votre appareil, vous pouvez ajouter une méthode qui utilisera la méthode loadFile
, à nouveau à l'aide de WebViewController
qui redirige String
contenant le chemin d'accès vers le fichier.
Vous devez d'abord créer un fichier contenant le code HTML. Pour ce faire, vous pouvez simplement ajouter le code HTML en tant que chaîne en haut de votre code dans le fichier menu.dart
, juste en dessous des importations.
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.
Pour créer File
et écrire la chaîne HTML dans le fichier, vous allez ajouter deux méthodes. _onLoadLocalFileExample
charge le fichier en fournissant le chemin d'accès sous forme de chaîne, qui est renvoyée par la méthode _prepareLocalFile()
. Ajoutez les méthodes suivantes à votre code :
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;
}
Charger une chaîne HTML
Il est plutôt simple d'afficher une page en fournissant une chaîne HTML. WebViewController
utilise une méthode appelée loadHtmlString
, qui vous permet d'utiliser la chaîne HTML comme argument. WebView
affiche alors la page HTML fournie. Ajoutez la méthode suivante à votre code :
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.
Ajouter les éléments du menu
Maintenant que les éléments sont définis et prêts à être utilisés, et que les méthodes comprenant toutes les fonctionnalités ont été créées, le menu peut être mis à jour. Ajoutez les entrées suivantes à l'énumération _MenuOptions
:
lib/src/menu.dart
enum _MenuOptions {
navigationDelegate,
userAgent,
javascriptChannel,
listCookies,
clearCookies,
addCookie,
setCookie,
removeCookie,
// Add from here ...
loadFlutterAsset,
loadLocalFile,
loadHtmlString,
// ... to here.
}
Maintenant que l'énumération est mise à jour, vous pouvez ajouter les options de menu et les associer aux méthodes d'assistance que vous venez d'ajouter. Mettez à jour la classe _MenuState
comme suit :
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'));
break;
case _MenuOptions.userAgent:
final userAgent = await widget.controller
.runJavaScriptReturningResult('navigator.userAgent');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('$userAgent'),
));
break;
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();''');
break;
case _MenuOptions.clearCookies:
await _onClearCookies();
break;
case _MenuOptions.listCookies:
await _onListCookies(widget.controller);
break;
case _MenuOptions.addCookie:
await _onAddCookie(widget.controller);
break;
case _MenuOptions.setCookie:
await _onSetCookie(widget.controller);
break;
case _MenuOptions.removeCookie:
await _onRemoveCookie(widget.controller);
break;
case _MenuOptions.loadFlutterAsset:
await _onLoadFlutterAssetExample(widget.controller, context);
break;
case _MenuOptions.loadLocalFile:
await _onLoadLocalFileExample(widget.controller, context);
break;
case _MenuOptions.loadHtmlString:
await _onLoadHtmlStringExample(widget.controller, context);
break;
}
},
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>(
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'),
),
],
);
}
Tester les éléments, le fichier et la chaîne HTML
Pour vérifier si le code précédemment mis en œuvre fonctionne correctement, vous pouvez l'exécuter sur votre appareil et cliquer sur l'un des éléments de menu nouvellement ajouté. Vous remarquerez que _onLoadFlutterAssetExample
utilise style.css
que nous avons ajouté pour remplacer l'en-tête du fichier HTML par la couleur bleue.
13. Terminé !
Félicitations ! Vous avez terminé l'atelier de programmation. Vous trouverez le code final de cet atelier de programmation dans le dossier atelier de programmation.
Pour aller plus loin, suivez les autres ateliers de programmation Flutter.