Informacje o tym ćwiczeniu (w Codelabs)
1. Wprowadzenie
Ostatnia aktualizacja: 2021-10-19
Za pomocą wtyczki WebView Flutter możesz dodać widżet WebView do aplikacji Flutter na Androida lub iOS. Na iOS widżet WebView jest obsługiwany przez WKWebView, a na Androidzie przez WebView. Wtyczka może renderować widżety Fluttera w widoku internetowym. Można na przykład renderować menu w widoku internetowym.
Co utworzysz
W tym laboratorium kodowania krok po kroku utworzysz aplikację mobilną z komponentem WebView za pomocą pakietu Flutter SDK. Twoja aplikacja będzie:
- Wyświetlanie treści w
WebView
- Wyświetlanie widżetów Fluttera ułożonych na
WebView
- Reagowanie na zdarzenia postępu wczytywania strony
- Sterowanie
WebView
za pomocąWebViewController
- Blokowanie witryn za pomocą
NavigationDelegate
- Obliczanie wartości wyrażeń JavaScript
- Obsługa wywołań zwrotnych z JavaScriptu za pomocą
JavascriptChannels
- Ustawianie, usuwanie, dodawanie i wyświetlanie plików cookie
- Wczytywanie i wyświetlanie kodu HTML z zasobów, plików lub ciągów znaków zawierających kod HTML
Czego się nauczysz
Z tego ćwiczenia z programowania dowiesz się, jak korzystać z webview_flutter
wtyczki na różne sposoby, m.in.:
- Jak skonfigurować wtyczkę
webview_flutter
- Jak nasłuchiwać zdarzeń postępu wczytywania strony
- Jak sterować nawigacją po stronie
- jak cofać i przewijać historię
WebView
, - Jak oceniać kod JavaScript, w tym korzystać z zwróconych wyników
- Rejestrowanie wywołań zwrotnych do wywoływania kodu Dart z JavaScriptu
- Zarządzanie plikami cookie
- Jak wczytywać i wyświetlać strony HTML z zasobów, plików lub ciągu znaków zawierającego kod HTML
Czego potrzebujesz
- Android Studio 4.1 lub nowszy (do programowania na Androida)
- Xcode 12 lub nowsza wersja (do tworzenia aplikacji na iOS)
- Pakiet SDK Flutter
- Edytor kodu, np. Android Studio lub Visual Studio Code.
2. Konfigurowanie środowiska programistycznego Fluttera
Aby ukończyć ten moduł, potrzebujesz 2 programów: pakietu Flutter SDK i edytora.
Codelab możesz uruchomić na dowolnym z tych urządzeń:
- fizyczne urządzenie z Android lub iOS podłączone do komputera i ustawione w trybie deweloperskim;
- Symulator iOS (wymaga zainstalowania narzędzi Xcode).
- Emulator Androida (wymaga konfiguracji w Android Studio).
3. Pierwsze kroki
Pierwsze kroki z Flutterem
Nowy projekt Fluttera można utworzyć na różne sposoby. Zarówno Android Studio, jak i Visual Studio Code udostępniają narzędzia do tego zadania. Aby utworzyć projekt, wykonaj procedury, do których prowadzą linki, lub uruchom te polecenia w wygodnym terminalu wiersza poleceń.
$ 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.
Dodawanie wtyczki WebView Flutter jako zależności
Najlepszym sposobem na dodanie dodatkowych funkcji do aplikacji Flutter jest użycie pakietów Pub. W tym laboratorium kodowania dodasz do projektu webview_flutter
wtyczkę. Uruchom w terminalu te polecenia.
$ cd webview_in_flutter $ flutter pub add webview_flutter Resolving dependencies... Downloading packages... collection 1.18.0 (1.19.0 available) leak_tracker 10.0.5 (10.0.7 available) leak_tracker_flutter_testing 3.0.5 (3.0.7 available) material_color_utilities 0.11.1 (0.12.0 available) + plugin_platform_interface 2.1.8 string_scanner 1.2.0 (1.3.0 available) test_api 0.7.2 (0.7.3 available) + webview_flutter 4.9.0 + webview_flutter_android 3.16.7 + webview_flutter_platform_interface 2.10.0 + webview_flutter_wkwebview 3.15.0 Changed 5 dependencies! 6 packages have newer versions incompatible with dependency constraints. Try `flutter pub outdated` for more information.
Jeśli sprawdzisz plik pubspec.yaml, zobaczysz, że w sekcji zależności znajduje się wiersz wtyczki webview_flutter
.
Konfigurowanie minimalnej wersji pakietu SDK na Androida
Aby używać wtyczki webview_flutter
na Androidzie, musisz ustawić wartość minSDK
na 20
. Zmodyfikuj plik android/app/build.gradle
w ten sposób:
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. Dodawanie widżetu WebView do aplikacji Flutter
W tym kroku dodasz do aplikacji element WebView
. WebView to hostowane widoki wbudowane, a Ty jako deweloper aplikacji możesz wybrać sposób hostowania tych widoków wbudowanych w aplikacji. Na Androidzie możesz wybrać między wyświetlaczami wirtualnymi (domyślnie w Androidzie) a kompozycją hybrydową. iOS zawsze używa jednak kompozycji hybrydowej.
Szczegółowe omówienie różnic między wirtualnymi wyświetleniami a kompozycją hybrydową znajdziesz w dokumentacji na temat hostowania natywnych widoków Androida i iOS w aplikacji Flutter za pomocą widoków platformy .
Wyświetlanie widoku WebView na ekranie
Zamień zawartość pliku lib/main.dart
na taką:
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,
),
);
}
}
Uruchomienie tego na urządzeniu z iOS lub Androidem spowoduje wyświetlenie elementu WebView jako pełnoekranowego okna przeglądarki, co oznacza, że przeglądarka będzie wyświetlana na urządzeniu w trybie pełnoekranowym bez żadnych obramowań ani marginesów. Podczas przewijania możesz zauważyć fragmenty strony, które wyglądają nieco dziwnie. Dzieje się tak, ponieważ JavaScript jest wyłączony, a prawidłowe renderowanie strony flutter.dev wymaga jego włączenia.
Uruchamianie aplikacji
Uruchom aplikację Flutter na iOS lub Androidzie, aby zobaczyć widok Webview, który wyświetla witrynę flutter.dev. Możesz też uruchomić aplikację w emulatorze Androida lub symulatorze iOS. Możesz zastąpić początkowy adres URL WebView np. adresem swojej witryny.
$ flutter run
Zakładając, że masz uruchomiony odpowiedni symulator lub emulator albo podłączone urządzenie fizyczne, po skompilowaniu i wdrożeniu aplikacji na urządzeniu powinna pojawić się mniej więcej taka zawartość:
5. Nasłuchiwanie zdarzeń wczytania strony
Widżet WebView
udostępnia kilka zdarzeń postępu wczytywania strony, których może nasłuchiwać Twoja aplikacja. Podczas WebView
cyklu ładowania strony wywoływane są 3 różne zdarzenia ładowania strony: onPageStarted
, onProgress
i onPageFinished
. W tym kroku zaimplementujesz wskaźnik ładowania strony. Dodatkowo pokaże to, że możesz renderować treści Fluttera w obszarze treści WebView
.
Dodawanie do aplikacji zdarzeń wczytania strony
Utwórz nowy plik źródłowy w lokalizacji lib/src/web_view_stack.dart
i wypełnij go tą treścią:
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,
),
],
);
}
}
Ten kod otacza widżet WebView
elementem Stack
, warunkowo nakładając element WebView
na element LinearProgressIndicator
, gdy odsetek wczytania strony jest mniejszy niż 100%. Ponieważ wiąże się to ze stanem programu, który zmienia się z czasem, ten stan jest przechowywany w klasie State
powiązanej z StatefulWidget
.
Aby użyć nowego widżetu WebViewStack
, zmodyfikuj plik lib/main.dart
w ten sposób:
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(),
);
}
}
Po uruchomieniu aplikacji, w zależności od warunków sieciowych i tego, czy przeglądarka ma w pamięci podręcznej stronę, do której przechodzisz, zobaczysz wskaźnik ładowania strony nałożony na obszar treści WebView
.
6. Praca z kontrolerem WebViewController
Uzyskiwanie dostępu do WebViewController z widżetu WebView
Widżet WebView
umożliwia programowe sterowanie za pomocą WebViewController
. Ten kontroler jest udostępniany po utworzeniu widżetu WebView
za pomocą wywołania zwrotnego. Asynchroniczny charakter dostępności tego kontrolera sprawia, że jest on doskonałym kandydatem do użycia w asynchronicznej klasie Completer<T>
w Dart.
Zaktualizuj lib/src/web_view_stack.dart
w ten sposób:
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,
),
],
);
}
}
Widżet WebViewStack
korzysta teraz z kontrolera utworzonego w otaczającym go widżecie. Umożliwi to udostępnianie kontrolera WebViewWidget
innym częściom aplikacji.
Tworzenie elementów sterujących nawigacją
Działający WebView
to jedno, ale możliwość przechodzenia w przód i w tył w historii strony oraz ponownego jej wczytywania to przydatne dodatki. Na szczęście dzięki WebViewController
możesz dodać tę funkcję do swojej aplikacji.
Utwórz nowy plik źródłowy w lokalizacji lib/src/navigation_controls.dart
i wypełnij go tymi informacjami:
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();
},
),
],
);
}
}
Ten widżet używa WebViewController
udostępnionego mu w momencie tworzenia, aby umożliwić użytkownikowi sterowanie WebView
za pomocą serii IconButton
.
Dodawanie elementów sterujących nawigacją do paska aplikacji
Po zapoznaniu się ze zaktualizowanym WebViewStack
i nowym NavigationControls
możesz teraz połączyć je w zaktualizowanym WebViewApp
. W tym miejscu tworzymy wspólny WebViewController
. Skoro w tej aplikacji element WebViewApp
znajduje się w górnej części drzewa widżetów, warto go utworzyć na tym poziomie.
Zaktualizuj plik lib/main.dart
w ten sposób:
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
);
}
}
Po uruchomieniu aplikacji powinna wyświetlić się strona internetowa z elementami sterującymi:
7. Śledzenie nawigacji za pomocą NavigationDelegate
WebView
udostępnia aplikacji NavigationDelegate,
, który umożliwia jej śledzenie i kontrolowanie nawigacji po stronach widżetu WebView
. Gdy nawigacja jest inicjowana przez WebView,
, na przykład gdy użytkownik kliknie link, wywoływana jest funkcja NavigationDelegate
. Wywołanie zwrotne NavigationDelegate
może służyć do kontrolowania, czy WebView
ma kontynuować nawigację.
Rejestrowanie niestandardowego delegata NavigationDelegate
W tym kroku zarejestrujesz NavigationDelegate
wywołanie zwrotne, aby zablokować nawigację do YouTube.com. Pamiętaj, że ta uproszczona implementacja blokuje też treści wbudowane w YouTube, które pojawiają się na różnych stronach dokumentacji interfejsu Flutter API.
Zaktualizuj lib/src/web_view_stack.dart
w ten sposób:
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,
),
],
);
}
}
W następnym kroku dodasz element menu, aby umożliwić testowanie NavigationDelegate
za pomocą klasy WebViewController
. Pozostawiamy czytelnikowi zadanie rozszerzenia logiki wywołania zwrotnego, aby blokować tylko nawigację na pełnej stronie do YouTube.com, ale nadal zezwalać na treści wbudowane w YouTube w dokumentacji interfejsu API.
8. Dodawanie przycisku menu do paska aplikacji
W kolejnych krokach utworzysz w widżecie AppBar
przycisk menu, który będzie służyć do sprawdzania kodu JavaScript, wywoływania kanałów JavaScript i zarządzania plikami cookie. Podsumowując, jest to przydatne menu.
Utwórz nowy plik źródłowy w lokalizacji lib/src/menu.dart
i wypełnij go tymi informacjami:
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'),
),
],
);
}
}
Gdy użytkownik wybierze opcję menu Przejdź do YouTube, zostanie wykonana metoda WebViewController
loadRequest
. Nawigacja zostanie zablokowana przez wywołanie zwrotne navigationDelegate
utworzone w poprzednim kroku.
Aby dodać menu do ekranu WebViewApp
, zmodyfikuj lib/main.dart
w ten sposób:
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),
);
}
}
Uruchom aplikację i kliknij pozycję menu Navigate to YouTube (Przejdź do YouTube). Powinien pojawić się SnackBar z informacją, że kontroler nawigacji zablokował przejście do YouTube.
9. Ocena kodu JavaScript
WebViewController
może oceniać wyrażenia JavaScript w kontekście bieżącej strony. JavaScript można oceniać na 2 sposoby: w przypadku kodu JavaScript, który nie zwraca wartości, użyj runJavaScript
, a w przypadku kodu JavaScript, który zwraca wartość, użyj runJavaScriptReturningResult
.
Aby włączyć JavaScript, musisz skonfigurować WebViewController
z właściwością javaScriptMode
ustawioną na JavascriptMode.unrestricted
. Domyślnie parametr javascriptMode
ma wartość JavascriptMode.disabled
.
Zaktualizuj klasę _WebViewStackState
, dodając ustawienie javascriptMode
w ten sposób:
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,
),
],
);
}
}
Teraz, gdy WebViewWidget
może wykonywać JavaScript, możesz dodać do menu opcję użycia metody runJavaScriptReturningResult
.
Korzystając z edytora lub klawiatury, przekształć klasę Menu w klasę StatefulWidget. Zmodyfikuj lib/src/menu.dart
tak, aby pasowało do tego:
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.
],
);
}
}
Gdy klikniesz opcję menu „Pokaż ciąg tekstowy User-Agent”, w elemencie Snackbar
pojawi się wynik wykonania wyrażenia JavaScript navigator.userAgent
. Po uruchomieniu aplikacji możesz zauważyć, że strona Flutter.dev wygląda inaczej. Oto wynik działania przy włączonym JavaScript.
10. Praca z kanałami JavaScript
Kanały JavaScript umożliwiają aplikacji rejestrowanie w kontekście JavaScriptu WebViewWidget
procedur obsługi wywołań zwrotnych, które można wywoływać w celu przekazywania wartości z powrotem do kodu Dart aplikacji. W tym kroku zarejestrujesz kanał SnackBar
, który będzie wywoływany z wynikiem XMLHttpRequest
.
Zaktualizuj klasę WebViewStack
w ten sposób:
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,
),
],
);
}
}
W przypadku każdego kanału JavaScript w Set
w kontekście JavaScript jest dostępny obiekt kanału jako właściwość okna o nazwie takiej samej jak nazwa kanału JavaScript name
. Użycie tej funkcji w kontekście JavaScriptu polega na wywołaniu funkcji postMessage
w kanale JavaScript w celu wysłania wiadomości, która jest przekazywana do wywołania zwrotnego onMessageReceived
o nazwie JavascriptChannel
.
Aby użyć dodanego wcześniej kanału JavaScript, dodaj kolejny element menu, który wykonuje XMLHttpRequest
w kontekście JavaScriptu i przekazuje wyniki za pomocą kanału JavaScript SnackBar
.
Teraz, gdy WebViewWidget
wie już o naszych kanałach JavaScript,
, dodasz przykład, aby rozwinąć aplikację. Aby to zrobić, dodaj dodatkowy znak PopupMenuItem
do klasy Menu
i dodaj dodatkową funkcję.
Zaktualizuj _MenuOptions
o dodatkową opcję menu, dodając wartość wyliczenia javascriptChannel
, i dodaj implementację do klasy Menu
w ten sposób:
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.
],
);
}
}
Ten kod JavaScript jest wykonywany, gdy użytkownik wybierze opcję menu JavaScript Channel Example (Przykład kanału 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();
Ten kod wysyła GET
żądanie do interfejsu Public IP Address API, który zwraca adres IP urządzenia. Ten wynik jest wyświetlany w SnackBar
przez wywołanie postMessage
na SnackBar
JavascriptChannel
.
11. Zarządzanie plikami cookie
Aplikacja może zarządzać plikami cookie w WebView
za pomocą klasy CookieManager
. Na tym etapie wyświetlisz listę plików cookie, wyczyścisz ją, usuniesz pliki cookie i ustawisz nowe. Dodaj wpisy do sekcji _MenuOptions
dla każdego przypadku użycia plików cookie w ten sposób:
lib/src/menu.dart
enum _MenuOptions {
navigationDelegate,
userAgent,
javascriptChannel,
// Add from here ...
listCookies,
clearCookies,
addCookie,
setCookie,
removeCookie,
// ... to here.
}
Pozostałe zmiany w tym kroku dotyczą klasy Menu
, w tym przekształcenia jej z bezstanowej w stanową.Menu
Ta zmiana jest ważna, ponieważ Menu
musi być właścicielem CookieManager
, a zmienny stan w widżetach bezstanowych to złe połączenie.
Dodaj CookieManager do klasy State w ten sposób:
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) {
// ...
Klasa _MenuState
będzie zawierać kod dodany wcześniej w klasie Menu
oraz nowo dodany element CookieManager
. W kolejnych sekcjach dodasz do _MenuState
funkcje pomocnicze, które będą wywoływane przez dodane później pozycje menu.
Wyświetlanie listy wszystkich plików cookie
Użyjesz JavaScriptu, aby uzyskać listę wszystkich plików cookie. Aby to zrobić, dodaj metodę pomocniczą na końcu klasy _MenuState
o nazwie _onListCookies
. Za pomocą metody runJavaScriptReturningResult
metoda pomocnicza wykonuje document.cookie
w kontekście JavaScriptu, zwracając listę wszystkich plików cookie.
Dodaj do klasy _MenuState
te elementy:
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.'),
),
);
}
Usuwanie wszystkich plików cookie
Aby wyczyścić wszystkie pliki cookie w obiekcie WebView, użyj metody clearCookies
klasy CookieManager
. Metoda zwraca obiekt Future<bool>
, który przyjmuje wartość true
, jeśli CookieManager
wyczyścił pliki cookie, a wartość false
, jeśli nie było plików cookie do wyczyszczenia.
Dodaj do klasy _MenuState
te elementy:
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),
),
);
}
Dodawanie pliku cookie
Plik cookie można dodać, wywołując JavaScript. Interfejs API używany do dodawania pliku cookie do dokumentu JavaScript jest szczegółowo opisany w MDN.
Dodaj do klasy _MenuState
te elementy:
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.'),
),
);
}
Ustawianie pliku cookie za pomocą klasy CookieManager
Pliki cookie można też ustawiać za pomocą interfejsu CookieManager w ten sposób:
Dodaj do klasy _MenuState
te elementy:
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.'),
),
);
}
Usuwanie pliku cookie
Usunięcie pliku cookie polega na dodaniu pliku cookie z datą ważności ustawioną w przeszłości.
Dodaj do klasy _MenuState
te elementy:
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.'),
),
);
}
Dodawanie elementów menu CookieManager
Pozostało tylko dodać opcje menu i połączyć je z dodanymi przed chwilą metodami pomocniczymi. Zaktualizuj klasę _MenuState
w ten sposób:
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.
],
);
}
Korzystanie z interfejsu CookieManager
Aby korzystać ze wszystkich funkcji, które właśnie dodano do aplikacji, wykonaj te czynności:
- Kliknij List cookies (Wyświetl listę plików cookie). Powinna zawierać listę plików cookie Google Analytics ustawionych przez flutter.dev.
- Kliknij Usuń pliki cookie. Powinien on potwierdzić, że pliki cookie zostały usunięte.
- Ponownie kliknij Usuń pliki cookie. Powinien on zgłosić, że nie ma plików cookie do wyczyszczenia.
- Kliknij List cookies (Wyświetl listę plików cookie). Powinien zgłosić, że nie ma plików cookie.
- Kliknij Dodaj plik cookie. Powinien on zgłosić dodanie pliku cookie.
- Kliknij Ustaw plik cookie. Powinien zgłosić ustawienie pliku cookie.
- Kliknij List cookies (Lista plików cookie), a następnie Remove cookie (Usuń plik cookie).
12. Wczytywanie zasobów, plików i ciągów HTML Flattera w komponencie WebView
Aplikacja może wczytywać pliki HTML różnymi metodami i wyświetlać je w widoku WebView. W tym kroku wczytasz zasób Fluttera określony w pliku pubspec.yaml
, wczytasz plik znajdujący się w określonej ścieżce i wczytasz stronę za pomocą ciągu HTML.
Jeśli chcesz wczytać plik znajdujący się w określonej ścieżce, musisz dodać path_provider
do pubspec.yaml
. Jest to wtyczka Fluttera do znajdowania często używanych lokalizacji w systemie plików.
W wierszu poleceń uruchom to polecenie:
$ flutter pub add path_provider
Aby wczytać komponent, musimy podać ścieżkę do niego w pubspec.yaml
. W pubspec.yaml
dodaj te wiersze:
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.
Aby dodać komponenty do projektu, wykonaj te czynności:
- Utwórz nowy katalog o nazwie
assets
w folderze głównym projektu. - Utwórz nowy katalog o nazwie
www
w folderzeassets
. - Utwórz nowy katalog o nazwie
styles
w folderzewww
. - Utwórz nowy plik o nazwie
index.html
w folderzewww
. - Utwórz nowy plik o nazwie
style.css
w folderzestyles
.
Skopiuj ten kod i wklej go do pliku index.html
:
assets/www/index.html
<!DOCTYPE html>
<!-- Copyright 2013 The Flutter Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<html lang="en">
<head>
<title>Load file or HTML string example</title>
<link rel="stylesheet" href="styles/style.css" />
</head>
<body>
<h1>Local demo page</h1>
<p>
This is an example page used to demonstrate how to load a local file or HTML
string using the <a href="https://pub.dev/packages/webview_flutter">Flutter
webview</a> plugin.
</p>
</body>
</html>
W pliku style.css użyj tych kilku wierszy, aby ustawić styl nagłówka HTML:
assets/www/styles/style.css
h1 {
color: blue;
}
Gdy zasoby są już skonfigurowane i gotowe do użycia, możesz wdrożyć metody potrzebne do wczytywania i wyświetlania zasobów, plików lub ciągów HTML w Flutterze.
Wczytywanie zasobu Fluttera
Aby wczytać utworzony właśnie komponent, wystarczy wywołać metodę loadFlutterAsset
, używając WebViewController
, i jako parametr podać ścieżkę do komponentu. Na końcu kodu dodaj tę metodę:
lib/src/menu.dart
Future<void> _onLoadFlutterAssetExample(
WebViewController controller, BuildContext context) async {
await controller.loadFlutterAsset('assets/www/index.html');
}
Wczytywanie pliku lokalnego
Aby wczytać plik na urządzeniu, możesz dodać metodę, która będzie korzystać z metody loadFile
, ponownie używając WebViewController
, która przyjmuje String
zawierający ścieżkę do pliku.
Najpierw musisz utworzyć plik zawierający kod HTML. Aby to zrobić, dodaj kod HTML jako ciąg znaków u góry kodu w pliku menu.dart
, tuż pod importami.
lib/src/menu.dart
import 'dart:io'; // Add this line,
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart'; // And this one.
import 'package:webview_flutter/webview_flutter.dart';
// Add from here ...
const String kExamplePage = '''
<!DOCTYPE html>
<html lang="en">
<head>
<title>Load file or HTML string example</title>
</head>
<body>
<h1>Local demo page</h1>
<p>
This is an example page used to demonstrate how to load a local file or HTML
string using the <a href="https://pub.dev/packages/webview_flutter">Flutter
webview</a> plugin.
</p>
</body>
</html>
''';
// ... to here.
Aby utworzyć File
i zapisać ciąg HTML w pliku, dodaj 2 metody. _onLoadLocalFileExample
wczyta plik, podając ścieżkę jako ciąg tekstowy zwracany przez metodę _prepareLocalFile()
. Dodaj do kodu te metody:
lib/src/menu.dart
Future<void> _onLoadLocalFileExample(
WebViewController controller, BuildContext context) async {
final String pathToIndex = await _prepareLocalFile();
await controller.loadFile(pathToIndex);
}
static Future<String> _prepareLocalFile() async {
final String tmpDir = (await getTemporaryDirectory()).path;
final File indexFile = File('$tmpDir/www/index.html');
await Directory('$tmpDir/www').create(recursive: true);
await indexFile.writeAsString(kExamplePage);
return indexFile.path;
}
Wczytaj ciąg HTML
Wyświetlanie strony przez podanie ciągu HTML jest dość proste. Obiekt WebViewController
ma metodę o nazwie loadHtmlString
, w której możesz podać ciąg HTML jako argument. WebView
wyświetli podaną stronę HTML. Dodaj do kodu tę metodę:
lib/src/menu.dart
Future<void> _onLoadFlutterAssetExample(
WebViewController controller, BuildContext context) async {
await controller.loadFlutterAsset('assets/www/index.html');
}
Future<void> _onLoadLocalFileExample(
WebViewController controller, BuildContext context) async {
final String pathToIndex = await _prepareLocalFile();
await controller.loadFile(pathToIndex);
}
static Future<String> _prepareLocalFile() async {
final String tmpDir = (await getTemporaryDirectory()).path;
final File indexFile = File('$tmpDir/www/index.html');
await Directory('$tmpDir/www').create(recursive: true);
await indexFile.writeAsString(kExamplePage);
return indexFile.path;
}
// Add here ...
Future<void> _onLoadHtmlStringExample(
WebViewController controller, BuildContext context) async {
await controller.loadHtmlString(kExamplePage);
}
// ... to here.
Dodawanie pozycji menu
Gdy komponenty są już skonfigurowane i gotowe do użycia, a metody ze wszystkimi funkcjami są utworzone, można zaktualizować menu. Dodaj do wyliczenia _MenuOptions
te wpisy:
lib/src/menu.dart
enum _MenuOptions {
navigationDelegate,
userAgent,
javascriptChannel,
listCookies,
clearCookies,
addCookie,
setCookie,
removeCookie,
// Add from here ...
loadFlutterAsset,
loadLocalFile,
loadHtmlString,
// ... to here.
}
Po zaktualizowaniu wyliczenia możesz dodać opcje menu i połączyć je z dodanymi przed chwilą metodami pomocniczymi. Zaktualizuj klasę _MenuState
w ten sposób:
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.
],
);
}
Testowanie komponentów, pliku i ciągu HTML
Aby sprawdzić, czy wdrożony kod działa, możesz go uruchomić na urządzeniu i kliknąć jeden z nowo dodanych elementów menu. Zwróć uwagę, jak tag _onLoadFlutterAssetExample
używa dodanego przez nas tagu style.css
, aby zmienić kolor nagłówka pliku HTML na niebieski.
13. Wszystko gotowe
Gratulacje!!! To już koniec tego laboratorium. Gotowy kod do tego ćwiczenia znajdziesz w repozytorium ćwiczeń.
Aby dowiedzieć się więcej, wypróbuj inne codelaby Fluttera.