1. Introdução
Última atualização: 19/10/2021
O plug-in do Flutter do WebView permite incluir um widget do WebView ao app do Flutter no Android ou no iOS. No iOS, o widget usa o WKWebView. Já no Android, ele usa um WebView (links em inglês). O plug-in pode renderizar widgets do Flutter pela visualização da Web. Por exemplo, um menu suspenso.
O que você vai criar
Neste codelab, você vai criar um app para dispositivos móveis com um WebView usando o SDK do Flutter. Esse app vai:
- mostrar conteúdo da Web em uma
WebView
; - mostrar widgets do Flutter empilhados sobre o
WebView
; - reagir a eventos de progresso de carregamento de página;
- controlar o
WebView
peloWebViewController
; - bloquear sites usando
NavigationDelegate
; - avaliar expressões de JavaScript;
- processar callbacks do JavaScript com
JavascriptChannels
; - definir, remover, adicionar ou mostrar cookies;
- carregar e mostrar HTML em recursos, arquivos ou strings que contenham HTML.
O que você vai aprender
Neste codelab, você vai aprender a usar o plug-in webview_flutter
(em inglês) de várias formas, por exemplo:
- Como configurar o plug-in
webview_flutter
. - Como detectar eventos de progresso de carregamento da página.
- Como controlar a navegação nas páginas.
- Como comandar o
WebView
para navegar pelo histórico. - Como avaliar o JavaScript, inclusive com os resultados retornados.
- Como registrar callbacks para chamar o código Dart no JavaScript.
- Como gerenciar cookies
- Como carregar e mostrar páginas em HTML em recursos, arquivos ou uma string que contenha HTML.
Pré-requisitos
- Android Studio 4.1 ou mais recente (para desenvolvimento no Android)
- Xcode 12 ou versão mais recente (para desenvolvimento no iOS)
- SDK do Flutter (em inglês)
- Um editor de código, como o Android Studio, o Visual Studio Code ou o Emacs (links em inglês).
2. Configurar o ambiente de desenvolvimento do Flutter
Você precisa de dois softwares para concluir este laboratório: o SDK do Flutter e um editor.
É possível executar o codelab usando qualquer um destes dispositivos:
- Um dispositivo físico Android ou iOS conectado ao seu computador e configurado para o modo de desenvolvedor.
- O simulador para iOS, que exige a instalação de ferramentas do Xcode.
- O Android Emulator, que requer configuração no Android Studio.
3. Primeiros passos
Primeiros passos com o Flutter
Há várias formas de criar um novo projeto do Flutter. O Android Studio e o Visual Studio Code oferecem ferramentas para isso (links em inglês). Siga os procedimentos indicados ou execute os comandos a seguir em um terminal de linha de comando.
$ 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.
Como adicionar o plug-in do Flutter do WebView como uma dependência
É fácil adicionar outros recursos a um app do Flutter usando os pacotes do Pub. Neste codelab, você vai adicionar o plug-in webview_flutter
ao seu projeto (links em inglês). Execute estes comandos no terminal.
$ cd webview_in_flutter $ flutter pub add webview_flutter
Se você inspecionar o arquivo pubspec.yaml, vai ver que ele tem uma linha na seção de dependências do plug-in webview_flutter
.
Configurar o minSDK do Android
Para usar o webview_flutter
plugin no Android você precisa definir minSDK
como 20
. Modifique o arquivo android/app/build.gradle
da seguinte maneira:
android/app/build.gradle
android {
//...
defaultConfig {
applicationId "com.example.webview_in_flutter"
minSdkVersion 20 // MODIFY
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
4. Como adicionar o widget do WebView ao app do Flutter
Nesta etapa, você vai adicionar um WebView
ao aplicativo. As WebViews são visualizações nativas hospedadas que o desenvolvedor de apps pode escolher como hospedar no app. No Android, as opções são a composição híbrida e a exibição virtual, que atualmente é o padrão do Android. O iOS sempre usa a composição híbrida.
Para ver mais informações sobre as diferenças entre as exibições virtuais e a composição híbrida, leia a documentação sobre como hospedar visualizações nativas do Android e do iOS no app do Flutter com visualizações da plataforma (em inglês).
Como colocar um WebView na tela
Substitua o conteúdo de lib/main.dart
pelo seguinte:
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,
),
);
}
}
Quando você executa essa funcionalidade no iOS ou no Android, o WebView aparece como uma janela do navegador sem margens no dispositivo, ou seja, o navegador é mostrado em tela cheia, sem bordas. Ao rolar a tela, você vai perceber que algumas partes da página podem parecer um pouco estranhas. Isso acontece porque o JavaScript está desativado, mas é necessário para renderizar o flutter.dev.
Executar o app
Execute o app do Flutter no iOS ou no Android para ver um WebView, que mostra o site flutter.dev (em inglês). Outra opção é executar o app em um Android Emulator ou no simulador de iOS. Se quiser, substitua o URL inicial do WebView pelo seu próprio site, por exemplo.
$ flutter run
Se você conectou o dispositivo físico certo ou está executando o simulador ou emulador apropriado, a imagem a seguir aparece após a compilação e a implantação do app:
5. Como detectar eventos de carregamento de página
O widget do WebView
fornece vários eventos do processo de carregamento da página que podem ser detectados pelo app. Durante o ciclo de carregamento de página do WebView
, três eventos de carregamento de página diferentes são disparados: onPageStarted
, onProgress
e onPageFinished
. Nesta etapa, você vai implementar um indicador de carregamento de página. Você também vai ver que é possível renderizar o conteúdo do Flutter na área de conteúdo do WebView
.
Como adicionar eventos de carregamento de página ao app
Crie um novo arquivo de origem em lib/src/web_view_stack.dart
e insira o seguinte conteúdo:
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,
),
],
);
}
}
Esse código uniu o widget do WebView
em Stack
, sobrepondo condicionalmente o WebView
com LinearProgressIndicator
quando a porcentagem de carregamento da página está abaixo de 100%. Como isso envolve um estado do programa que muda com o tempo, você armazenou esse estado em uma classe State
associada a StatefulWidget
.
Para usar esse novo widget do WebViewStack
, modifique o lib/main.dart desta forma:
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(),
);
}
}
Quando você executa o app, dependendo das condições da sua rede e caso o navegador armazene em cache ou não a página acessada, um indicador de carregamento de página aparece sobre a área do conteúdo do WebView
.
6. Como trabalhar com o WebViewController
Como acessar o WebViewController pelo widget do WebView
O widget do WebView
permite o controle programático com WebViewController
. Esse controlador é disponibilizado após a criação do widget do WebView
com um callback. Como a disponibilidade desse controlador é assíncrona, ele é um forte candidato para a classe assíncrona Completer<T>
do Dart.
Atualize o lib/src/web_view_stack.dart
desta maneira:
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,
),
],
);
}
}
Agora o WebViewStack
usa um controle criado no widget próximo. Assim, o controle do WebViewWidget
pode ser compartilhado facilmente com outras partes do app.
Como criar controles de navegação
O WebView
pode até estar funcionando, mas ainda falta poder atualizar a página e avançar ou voltar o histórico. Felizmente, é possível usar um WebViewController
para incluir essa funcionalidade no app.
Crie um arquivo de origem em lib/src/navigation_controls.dart
e insira o seguinte:
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();
},
),
],
);
}
}
Esse widget usa o WebViewController
compartilhado no momento da criação para permitir que o usuário controle WebView
em uma série de IconButton
s.
Como adicionar controles de navegação à barra de apps
Depois de atualizar WebViewStack
e criar NavigationControls
, vamos colocar tudo isso em um WebViewApp
atualizado. É aqui que vamos criar o WebViewController
compartilhado. Como WebViewApp
está na parte de cima da árvore de widgets desse app, esse é o nível ideal para fazermos isso.
Atualize o arquivo lib/main.dart
desta forma:
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
);
}
}
A execução do app revela uma página da Web com controles:
7. Como rastrear a navegação com NavigationDelegate
O WebView
fornece ao app com NavigationDelegate,
, que permite acompanhar e controlar a navegação na página do widget do WebView
. Quando uma navegação é iniciada pelo WebView,
, como quando um usuário clica em um link, NavigationDelegate
é chamado. É possível usar o callback NavigationDelegate
para decidir se o WebView
continua a navegação.
Registrar NavigationDelegate personalizado
Nesta etapa, você vai registrar um callback NavigationDelegate
para bloquear a navegação no YouTube.com. Essa implementação simplista também bloqueia conteúdo inline do YouTube, que aparece em várias páginas de documentação da API do Flutter.
Atualize o lib/src/web_view_stack.dart
desta forma:
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,
),
],
);
}
}
Na próxima etapa, você vai adicionar um item de menu para ativar o teste de NavigationDelegate
usando a classe WebViewController
. Nesse exercício, o leitor aumenta a lógica do callback para bloquear apenas a navegação de página inteira no YouTube.com, mas permite o conteúdo inline do YouTube na documentação da API.
8. Como adicionar um botão de menu à barra de apps
Nas próximas etapas, você vai criar um botão de menu no widget de AppBar
usado para avaliar o JavaScript, invocar canais JavaScript e gerenciar cookies. Resumindo, um menu muito útil.
Crie um arquivo de origem em lib/src/menu.dart
e insira o seguinte:
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'),
),
],
);
}
}
Quando o usuário seleciona a opção de menu Navigate to YouTube, o método loadRequest
do WebViewController
é executado. Essa navegação é bloqueada pelo callback navigationDelegate
que você criou na etapa anterior.
Para adicionar o menu à tela do WebViewApp
, modifique lib/main.dart
da seguinte maneira:
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),
);
}
}
Execute o app e toque no item de menu Navigate to YouTube. Uma snackbar informa que o controlador de navegação bloqueou o acesso ao YouTube.
9. Como avaliar o JavaScript
WebViewController
pode avaliar expressões JavaScript no contexto da página atual. Para isso, existem duas opções: se o código JavaScript retornar um valor, use runJavaScriptReturningResult
. Caso contrário, use runJavaScript
.
Para ativar o JavaScript, defina a propriedade javaScriptMode
como JavascriptMode.unrestricted
no widget do WebViewController
. Por padrão, javascriptMode
é definido como JavascriptMode.disabled
.
Atualize a classe _WebViewStackState
adicionando a configuração javascriptMode
da seguinte maneira:
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,
),
],
);
}
}
Agora que o WebViewWidget
pode executar o JavaScript, é possível incluir uma opção no menu para usar o método runJavaScriptReturningResult
.
Use o editor ou o teclado para converter a classe "Menu" em um "StatefulWidget". Modifique lib/src/menu.dart
da seguinte forma:
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'),
),
],
);
}
}
Quando você toca na opção do menu "Show user-agent", o resultado da execução da expressão JavaScript navigator.userAgent
aparece em um Snackbar
. Ao executar o app, a página do Flutter.dev pode parecer diferente. Isso acontece porque o JavaScript está ativado.
10. Como trabalhar com canais JavaScript
Os canais JavaScript permitem que o app registre gerenciadores de callback no contexto dessa linguagem para o WebViewWidget
. Eles podem ser invocados para transmitir valores de volta ao código Dart do app. Nesta etapa, você vai registrar um canal de SnackBar
que será chamado com o resultado de uma XMLHttpRequest
.
Atualize a classe WebViewStack
desta forma:
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,
),
],
);
}
}
Para cada canal JavaScript no Set
, um objeto é disponibilizado no contexto dessa linguagem como uma propriedade de janela com o mesmo nome de name
. Para usar o objeto no contexto do JavaScript, é preciso chamar postMessage
no canal para enviar uma mensagem que é transmitida ao gerenciador de callback onMessageReceived
do JavascriptChannel
.
Para usar o canal JavaScript adicionado acima, inclua outro item de menu que execute um XMLHttpRequest
no contexto dessa linguagem e transmita os resultados de volta usando o canal JavaScript do SnackBar
.
Agora que WebViewWidget
já sabe sobre nossos canais JavaScript ,
você vai adicionar um exemplo para expandir o app. Para isso, adicione mais PopupMenuItem
à classe Menu
e inclua a funcionalidade extra.
Atualize _MenuOptions
com a opção de menu extra: adicione o valor de enumeração javascriptChannel
e uma implementação à classe Menu
da seguinte forma:
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'),
),
],
);
}
}
Esse JavaScript é executado quando o usuário escolhe a opção de menu JavaScript Channel Example.
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();
Esse código envia uma solicitação GET
a uma API de endereço IP público para retornar o endereço IP do dispositivo. Esse resultado aparece em SnackBar
invocando postMessage
em JavascriptChannel
de SnackBar
.
11. Como gerenciar cookies
Seu app pode gerenciar cookies no WebView
usando a classe CookieManager
. Nesta etapa, você vai definir e excluir cookies e mostrar e limpar uma lista de cookies. Adicione entradas a _MenuOptions
para cada um dos casos de uso de cookies da seguinte maneira:
lib/src/menu.dart
enum _MenuOptions {
navigationDelegate,
userAgent,
javascriptChannel,
// Add from here ...
listCookies,
clearCookies,
addCookie,
setCookie,
removeCookie,
// ... to here.
}
As outras mudanças feitas nesta etapa são relacionadas à classe Menu
, incluindo a conversão da classe Menu
de sem estado para com estado. Essa mudança é importante porque Menu
precisa ser o proprietário de CookieManager
, e o estado mutável em widgets sem estado não é apropriado.
Adicione o CookieManager à classe de estado resultante, como mostramos a seguir:
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) {
// ...
A classe _MenuState
vai conter o código incluído na classe Menu
e o CookieManager
adicionado. Nas próximas seções, você vai adicionar funções auxiliares a _MenuState
, que será invocado pelos itens de menu a serem adicionados.
Receber uma lista de todos os cookies
Você vai usar o JavaScript para acessar uma lista com todos os cookies. Para isso, adicione o método auxiliar _onListCookies
no fim da classe _MenuState
. Seu método auxiliar usa o método runJavaScriptReturningResult
para executar document.cookie
no contexto do JavaScript, retornando uma lista de todos os cookies.
Adicione este código à 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.'),
),
);
}
Limpar todos os cookies
Para limpar todos os cookies no WebView, use o método clearCookies
da classe CookieManager
. O método retorna Future<bool>
, que é resolvido para true
quando CookieManager
limpa os cookies, e para false
quando não há cookies.
Adicione este código à 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),
),
);
}
Adicionar um cookie
É possível invocar o JavaScript para adicionar cookies. A API usada para adicionar um cookie a um documento JavaScript é documentada em detalhes no MDN.
Adicione este código à 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.'),
),
);
}
Como definir cookies com CookieManager
Uma opção para definir cookies é usar CookieManager (em inglês) como descrito a seguir.
Adicione este código à 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.'),
),
);
}
Remover cookies
Defina uma data de vencimento passada em um cookie para que ele seja excluído.
Adicione este código à 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.'),
),
);
}
Como adicionar os itens de menu de CookieManager
Agora você só precisa incluir e conectar as opções do menu aos métodos auxiliares que você acabou de adicionar. Atualize a classe _MenuState
desta forma:
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'),
),
],
);
}
Como usar CookieManager
Para usar todas as funcionalidades que você acabou de adicionar ao app, siga estas etapas:
- Selecione Listar cookies. Os cookies do Google Analytics definidos pelo flutter.dev são listados.
- Selecione Limpar cookies. A mensagem informa que os cookies realmente foram apagados.
- Selecione Limpar cookies mais uma vez. A mensagem informa que não há cookies para limpar.
- Selecione Listar cookies. A mensagem informa que não há cookies.
- Selecione Adicionar cookie. A mensagem informa que o cookie foi adicionado.
- Selecione Definir cookie. A mensagem informa que o cookie foi definido.
- Selecione Listar cookies e, em seguida, Remover cookie.
12. Carregar recursos, arquivos e strings HTML do Flutter no WebView
O app pode carregar arquivos HTML usando diferentes métodos e mostrar na WebView. Nesta etapa, você vai carregar um recurso do Flutter especificado no arquivo pubspec.yaml
, um arquivo localizado no caminho especificado e uma página usando uma string HTML.
Se você quer carregar um arquivo localizado em um caminho especificado, adicione path_provider
a pubspec.yaml
. Esse plug-in do Flutter ajuda a encontrar locais usados com frequência no sistema de arquivos.
Insira os seguintes valores na linha de comando:
$ flutter pub add path_provider
Para carregar o recurso, especifique o caminho dele em pubspec.yaml
. Em pubspec.yaml
, adicione estas linhas:
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.
Para adicionar os recursos ao seu projeto, siga estas etapas:
- Crie um novo diretório com o nome
assets
na pasta raiz do projeto. - Crie um novo diretório com o nome
www
na pastaassets
. - Crie um novo diretório com o nome
styles
na pastawww
. - Crie um novo arquivo com o nome
index.html
na pastawww
. - Crie um novo arquivo com o nome
style.css
na pastastyles
.
Copie e cole o seguinte código no arquivo 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>
Em style.css, use as seguintes linhas para definir o estilo do cabeçalho HTML:
h1 {
color: blue;
}
Agora que os recursos estão definidos e prontos para uso, é possível implementar os métodos necessários para carregar e mostrar recursos, arquivos ou strings HTML do Flutter.
Carregar o recurso do Flutter
Para carregar o recurso que você acabou de criar, chame o método loadFlutterAsset
usando WebViewController
e forneça o caminho do recurso como parâmetro. Adicione o seguinte método no final do seu código:
lib/src/menu.dart
Future<void> _onLoadFlutterAssetExample(
WebViewController controller, BuildContext context) async {
await controller.loadFlutterAsset('assets/www/index.html');
}
Carregar um arquivo local
Para carregar um arquivo no seu dispositivo, adicione um método que vai usar o método loadFile
com WebViewController
, que usa a String
com o caminho do arquivo.
Primeiro, crie um arquivo com o código HTML. Para isso, adicione o código HTML como uma string na parte de cima do código no arquivo menu.dart
, logo abaixo das importações.
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.
Para criar File
e gravar a string HTML no arquivo, adicione dois métodos. _onLoadLocalFileExample
vai carregar o arquivo fornecendo o caminho como uma string retornada pelo método _prepareLocalFile()
. Adicione estes métodos ao código:
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;
}
Carregar uma string HTML
É bem simples mostrar uma página fornecendo uma string HTML. WebViewController
tem um método para isso chamado loadHtmlString
, em que você fornece a string HTML como um argumento. Depois disso, WebView
mostra a página HTML fornecida. Adicione este método ao seu código:
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.
Adicionar os itens de menu
Agora que os recursos foram definidos e estão prontos para uso, e os métodos com todas as funcionalidades foram criados, já podemos atualizar o menu. Adicione as seguintes entradas ao tipo enumerado _MenuOptions
:
lib/src/menu.dart
enum _MenuOptions {
navigationDelegate,
userAgent,
javascriptChannel,
listCookies,
clearCookies,
addCookie,
setCookie,
removeCookie,
// Add from here ...
loadFlutterAsset,
loadLocalFile,
loadHtmlString,
// ... to here.
}
Agora que a enumeração foi atualizada, inclua as opções do menu e conecte a eles os métodos auxiliares que você adicionou. Atualize a classe _MenuState
desta forma:
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'),
),
],
);
}
Como testar os recursos, o arquivo e a string HTML
Agora verifique se tudo deu certo. No seu dispositivo, execute o código que você acabou de implementar e clique em um dos itens de menu recém-adicionados. Veja como _onLoadFlutterAssetExample
usa o style.css
que adicionamos para deixar o cabeçalho do arquivo HTML na cor azul.
13. Pronto!
Parabéns! Você concluiu o codelab. O código completo deste codelab está disponível no repositório do codelab (em inglês).
Para saber mais, veja os outros codelabs do Flutter (link em inglês).