1. Wprowadzenie
Ostatnia aktualizacja: 19.10.2021
Dzięki wtyczce WebView Flutter możesz dodać widżet WebView do aplikacji Flutter na Androida lub iOS. Na iOS widżet WebView korzysta z komponentu WKWebView, a na Androidzie – z komponentu WebView. Wtyczka może renderować widżety Flutter w widoku internetowym. W ten sposób możesz na przykład renderować menu rozwijane w widoku witryny.
Co utworzysz
W ramach tego ćwiczenia w Codelabs dowiesz się, jak za pomocą pakietu SDK Flutter stworzyć aplikację mobilną z komponentem WebView. Twoja aplikacja będzie:
- Wyświetlaj treści z internetu w
WebView
- Wyświetlaj widżety Flutter nałożone nad elementem
WebView
- Reagowanie na zdarzenia postępu wczytywania strony
- Sterowanie:
WebView
za pomocą:WebViewController
- Blokuj strony za pomocą interfejsu
NavigationDelegate
- Obliczanie wartości wyrażeń JavaScript
- Obsługuj wywołania zwrotne z JavaScriptu za pomocą
JavascriptChannels
- Ustawianie, usuwanie, dodawanie i wyświetlanie plików cookie
- Ładuj i wyświetlaj kod HTML z zasobów, plików lub ciągów tekstowych zawierających kod HTML
Czego się nauczysz
Z tego ćwiczenia w Codelabs dowiesz się, jak korzystać z wtyczki webview_flutter
na różne sposoby, w tym:
- Jak skonfigurować wtyczkę
webview_flutter
- Jak nasłuchiwać zdarzeń postępu wczytywania strony
- Jak sterować nawigacją na stronie
- Jak nakazać urządzeniu
WebView
przejście do przodu i do tyłu w jego historii - Ocena kodu JavaScript, w tym korzystanie z zwróconych wyników
- Jak rejestrować wywołania zwrotne w celu wywołania kodu Dart z JavaScriptu
- Zarządzanie plikami cookie
- Jak wczytywać i wyświetlać strony HTML z zasobów lub plików lub ciągu tekstowego zawierającego HTML
Czego potrzebujesz
- Android Studio 4.1 lub nowszy (na potrzeby programowania na Androida)
- Xcode 12 lub nowsza wersja (na potrzeby programowania na iOS)
- Pakiet SDK Flutter
- Edytor kodu, taki jak Android Studio, Visual Studio Code lub Emacs.
2. Konfigurowanie środowiska programistycznego Flutter
Aby ukończyć ten moduł, potrzebujesz 2 oprogramowania: pakietu SDK Flutter i edytora.
Ćwiczenie z programowania możesz uruchomić na dowolnym z tych urządzeń:
- Fizyczne urządzenie z Androidem lub iOS podłączone do komputera i ustawione w trybie programisty.
- Symulator iOS (wymaga zainstalowania narzędzi Xcode).
- Emulator Androida (wymaga skonfigurowania Android Studio).
3. Pierwsze kroki
Wprowadzenie do Flutter
Nowy projekt Flutter możesz utworzyć na wiele sposobów. Narzędzia do tego zadania zawierają Android Studio i Visual Studio Code. Utwórz projekt zgodnie z powiązanymi procedurami lub wykonaj poniższe polecenia w praktycznym 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ść
Dodatkowe możliwości do aplikacji Flutter możesz łatwo dodać za pomocą pakietów Pub. W ramach tego ćwiczenia w Codelabs dodasz do projektu wtyczkę webview_flutter
. Uruchom w terminalu te polecenia.
$ cd webview_in_flutter $ flutter pub add webview_flutter Resolving dependencies... Downloading packages... leak_tracker 10.0.4 (10.0.5 available) leak_tracker_flutter_testing 3.0.3 (3.0.5 available) material_color_utilities 0.8.0 (0.11.1 available) meta 1.12.0 (1.14.0 available) + plugin_platform_interface 2.1.8 test_api 0.7.0 (0.7.1 available) + webview_flutter 4.7.0 + webview_flutter_android 3.16.0 + webview_flutter_platform_interface 2.10.0 + webview_flutter_wkwebview 3.13.0 Changed 5 dependencies! 5 packages have newer versions incompatible with dependency constraints. Try `flutter pub outdated` for more information.
Jeśli sprawdzisz plik pubspec.yaml, zobaczysz, że zawiera on wiersz w sekcji zależności wtyczki webview_flutter
.
Konfigurowanie pakietu Android minSDK
Aby używać wtyczki webview_flutter
na Androidzie, ustaw 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 WebView
. Komponenty WebView to hostowane widoki natywne, a deweloper aplikacji może zdecydować, jak chcesz hostować te widoki natywne w swojej aplikacji. Na Androidzie do wyboru masz ekrany wirtualne (obecnie domyślne w przypadku Androida) i kompozycję hybrydową. Jednak system iOS zawsze używa kompozycji hybrydowej.
Dokładniejsze omówienie różnic między wyświetlaczami wirtualnymi a kompozycją hybrydową znajdziesz w dokumentacji na temat hostowania natywnych widoków na Androida i iOS w aplikacji Flutter z widokami platformy.
Umieszczanie komponentu 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,
),
);
}
}
Gdy uruchomisz tę funkcję na urządzeniach z iOS lub Androidem, komponent WebView będzie wyświetlany na urządzeniu jako okno przeglądarki z pełnym spadem, co oznacza, że przeglądarka będzie wyświetlana na urządzeniu w trybie pełnoekranowym bez żadnych obramowania czy marginesów. Podczas przewijania możesz zauważyć pewne fragmenty strony, które mogą wyglądać trochę dziwnie. Dzieje się tak, ponieważ JavaScript jest obecnie wyłączony, a prawidłowe renderowanie strony flutter.dev wymaga JavaScriptu.
Uruchamianie aplikacji
Uruchom aplikację Flutter na iOS lub Androida, aby zobaczyć komponent WebView wyświetlający stronę internetową flutter.dev. Możesz też uruchomić aplikację w emulatorze Androida lub symulatorze iOS. Możesz zastąpić początkowy adres URL komponentu WebView przykładem własnej 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 zobaczysz coś takiego:
5. Nasłuchiwanie zdarzeń wczytania strony
Widżet WebView
udostępnia kilka zdarzeń postępu wczytywania strony, których może rejestrować aplikacja. W cyklu wczytywania strony WebView
wywoływane są 3 różne zdarzenia wczytywania strony: onPageStarted
, onProgress
i onPageFinished
. W tym kroku zaimplementujesz wskaźnik wczytywania strony. Zobaczysz też, że możesz renderować treści Flutter w obszarze treści WebView
.
Dodawanie zdarzeń wczytywania strony do aplikacji
Utwórz nowy plik źródłowy pod adresem 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
w Stack
, warunkowo nakładając się na WebView
elementem LinearProgressIndicator
, gdy odsetek wczytywania strony jest mniejszy niż 100%. Obejmuje to stan programu, który zmienia się z czasem, dlatego ten stan został zapisany w klasie State
powiązanej z zasadą StatefulWidget
.
Aby korzystać z 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 zapisała stronę, do której przechodzisz, w pamięci podręcznej, nad obszarem treści WebView
nałożony zostanie wskaźnik wczytywania strony.
6. Praca z komponentem WebViewController
Uzyskiwanie dostępu do kontrolera WebViewController z widżetu WebView
Widżet WebView
umożliwia zautomatyzowaną kontrolę za pomocą interfejsu WebViewController
. Ten kontroler jest udostępniany przez wywołanie zwrotne po utworzeniu widżetu WebView
. Asynchroniczny charakter dostępności tego kontrolera sprawia, że jest on doskonałym kandydatem do asynchronicznej klasy Completer<T>
Dart.
Zaktualizuj plik 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
używa teraz kontrolera utworzonego w otaczającym widżecie. Umożliwi to łatwe udostępnianie kontrolera urządzenia WebViewWidget
innym częściom aplikacji.
Tworzenie elementów sterujących nawigacją
Działanie WebView
to jedno, ale przydaje się możliwość przechodzenia do tyłu i do przodu w historii strony oraz ponownego jej wczytywania. Na szczęście dzięki WebViewController
możesz dodać tę funkcję do swojej aplikacji.
Utwórz nowy plik źródłowy pod adresem lib/src/navigation_controls.dart
i wypełnij go tym:
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 korzysta z obiektów WebViewController
udostępnionych podczas tworzenia, aby umożliwić użytkownikowi kontrolowanie WebView
za pomocą serii IconButton
.
Dodawanie elementów nawigacyjnych do paska aplikacji
Masz teraz zaktualizowane WebViewStack
i nowo utworzone NavigationControls
. Teraz możesz je połączyć w zaktualizowany WebViewApp
. Tutaj tworzymy wspólny WebViewController
. Element WebViewApp
znajduje się w górnej części drzewa widżetów, więc warto utworzyć go 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 opcjami:
7. Śledzenie nawigacji za pomocą NavigationDelegate
WebView
udostępnia Twojej aplikacji uprawnienie NavigationDelegate,
, które umożliwia aplikacji śledzenie i kontrolowanie nawigacji na stronie w widżecie WebView
. Jeśli WebView,
inicjuje nawigację, np. gdy użytkownik kliknie link, wywoływana jest funkcja NavigationDelegate
. Wywołanie zwrotne NavigationDelegate
pozwala określić, czy WebView
ma kontynuować nawigację.
Rejestrowanie niestandardowego obiektu NavigationDelegate
Na tym etapie zarejestrujesz wywołanie zwrotne NavigationDelegate
, które zablokuje nawigację na YouTube.com. Pamiętaj, że ta uproszczona implementacja blokuje też wbudowane treści z 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 pozycję menu, która pozwoli na testowanie elementu NavigationDelegate
przy użyciu klasy WebViewController
. Pozostawiamy czytelnikowi ćwiczenie w zakresie udoskonalenia logiki wywołania zwrotnego w celu zablokowania jedynie możliwości przejścia na całą stronę do YouTube.com i pozwolenia na wbudowane treści z YouTube w dokumentacji interfejsu API.
8. Dodawanie przycisku menu do paska aplikacji
W ciągu kilku następnych kroków utworzysz w widżecie AppBar
przycisk menu, który będzie służyć do oceny kodu JavaScript, wywoływania kanałów JavaScriptu i zarządzania plikami cookie. Podsumowując, menu jest naprawdę przydatne.
Utwórz nowy plik źródłowy pod adresem lib/src/menu.dart
i wypełnij go tym:
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 loadRequest
stosowana przez WebViewController
. Ta nawigacja zostanie zablokowana przez wywołanie zwrotne navigationDelegate
utworzone w poprzednim kroku.
Aby dodać menu do ekranu urządzenia 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 Przejdź do YouTube. Powinien wyświetlić się pasek powiadomień informujący, że kontroler nawigacyjny zablokował nawigację do YouTube.
9. Ocena JavaScriptu
WebViewController
może oceniać wyrażenia JavaScript w kontekście bieżącej strony. Istnieją 2 sposoby oceny kodu JavaScript: w przypadku kodu JavaScript, który nie zwraca wartości, użyj metody 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 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,
),
],
);
}
}
Gdy WebViewWidget
może już wykonywać JavaScript, możesz dodać do menu opcję użycia metody runJavaScriptReturningResult
.
Za pomocą edytora lub klawiatury przekonwertuj klasę Menu na StatefulWidget. Zmień lib/src/menu.dart
, aby pasował do tych:
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 „Pokaż klienta użytkownika”, opcję menu, wynik wykonania wyrażenia JavaScript navigator.userAgent
zostanie wyświetlony w Snackbar
. Po uruchomieniu aplikacji możesz zauważyć, że strona Flutter.dev wygląda inaczej. Jest to wynikiem działania z włączoną obsługą JavaScriptu.
10. Praca z kanałami JavaScript
Kanały JavaScript umożliwiają aplikacji rejestrowanie modułów obsługi wywołań zwrotnych w kontekście JavaScriptu WebViewWidget
, 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 w wyniku 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,
),
],
);
}
}
Dla każdego kanału JavaScript w Set
obiekt kanału jest udostępniany w kontekście JavaScriptu jako właściwość okna o nazwie takiej samej jak kanał JavaScript name
. Użycie tej opcji w kontekście JavaScriptu wiąże się z wywołaniem postMessage
w kanale JavaScript w celu wysłania wiadomości, która jest przekazywana do modułu obsługi wywołania zwrotnego onMessageReceived
nazwanego elementu JavascriptChannel
.
Aby skorzystać z dodanego powyżej kanału JavaScript, dodaj kolejny element menu, który wykonuje polecenie XMLHttpRequest
w kontekście JavaScriptu i zwraca wyniki za pomocą kanału JavaScript SnackBar
.
Skoro WebViewWidget
zna już nasze kanały JavaScript,,
możesz dodać przykład, by jeszcze bardziej rozwinąć aplikację. Aby to zrobić, dodaj do klasy Menu
element PopupMenuItem
i nową funkcję.
Zaktualizuj _MenuOptions
o dodatkową opcję menu, dodając wartość wyliczeniową javascriptChannel
, i dodaj implementację do klasy Menu
w następujący 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 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 żądanie GET
do interfejsu Public IP Address API i zwraca adres IP urządzenia. Ten wynik jest wyświetlany w polu SnackBar
po wywołaniu funkcji postMessage
w SnackBar
JavascriptChannel
.
11. Zarządzanie plikami cookie
Aplikacja może zarządzać plikami cookie w elemencie WebView
za pomocą klasy CookieManager
. W tym kroku wyświetli się lista plików cookie, wyczyść listę plików cookie i usuniesz pliki cookie, a także utworzysz nowe. Dodaj do _MenuOptions
następujące wpisy dla poszczególnych przypadków użycia plików cookie:
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 klasy Menu
z bezstanowego na stanową. Ta zmiana jest ważna, ponieważ element Menu
musi należeć do elementu CookieManager
, a zmienny stan widżetów bezstanowych to zła kombinacja.
Dodaj CookieManager do wynikowej klasy State w następujący 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
, a także nowo dodany element CookieManager
. W kolejnej serii sekcji dodasz do funkcji _MenuState
funkcje pomocnicze, które z kolei będą wywoływane przez pozycje menu, które nie zostały jeszcze dodane.
Uzyskiwanie listy wszystkich plików cookie
Użycie JavaScriptu spowoduje wyświetlenie listy wszystkich plików cookie. Aby to osiągnąć, dodaj metodę pomocniczą na końcu klasy _MenuState
o nazwie _onListCookies
. Metoda runJavaScriptReturningResult
wykonuje polecenie document.cookie
w kontekście JavaScriptu, zwracając listę wszystkich plików cookie.
Dodaj do klasy _MenuState
ten kod:
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.'),
),
);
}
Czyszczenie wszystkich plików cookie
Aby usunąć wszystkie pliki cookie w komponencie WebView, użyj metody clearCookies
klasy CookieManager
. Metoda zwraca wartość Future<bool>
, która zwraca wartość true
, jeśli funkcja CookieManager
wyczyściła pliki cookie, lub false
, jeśli nie było plików cookie do wyczyszczenia.
Dodaj do klasy _MenuState
ten kod:
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ć przez wywołanie JavaScriptu. Interfejs API służący do dodawania pliku cookie do dokumentu JavaScript jest dokładnie udokumentowany w MDN.
Dodaj do klasy _MenuState
ten kod:
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ą CookieManager
Pliki cookie można też tworzyć za pomocą parametru CookieManager w podany niżej sposób.
Dodaj do klasy _MenuState
ten kod:
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
ten kod:
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 pozycji menu CookieManager
Teraz musisz tylko dodać opcje menu i połączyć je z dodanymi właśnie 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 narzędzia CookieManager
Aby korzystać ze wszystkich funkcji dodanych przed chwilą do aplikacji, wykonaj te czynności:
- Wybierz Wyświetl listę plików cookie. Powinna ona zawierać listę plików cookie Google Analytics utworzonych przez flutter.dev.
- Wybierz Wyczyść pliki cookie. Powinna tam być informacja, że pliki cookie zostały usunięte.
- Ponownie wybierz Wyczyść pliki cookie. Powinna pojawić się informacja, że nie ma plików cookie dostępnych do usunięcia.
- Wybierz Wyświetl listę plików cookie. Powinna pojawić się informacja o braku plików cookie.
- Wybierz Dodaj plik cookie. Plik cookie powinien być raportowany jako dodany.
- Wybierz Ustaw plik cookie. Powinien zgłaszać plik cookie jako ustawiony.
- Wybierz Lista plików cookie, a na koniec kliknij Usuń plik cookie.
12. Wczytywanie zasobów, plików i ciągów HTML z Flutter w komponencie WebView
Aplikacja może wczytywać pliki HTML na różne sposoby i wyświetlać je w komponencie WebView. W tym kroku wczytasz zasób Flutter określony w pliku pubspec.yaml
, wczytasz plik znajdujący się w podanej ścieżce i wczytasz stronę za pomocą ciągu HTML.
Jeśli chcesz wczytać plik znajdujący się pod określoną ścieżką, path_provider
do pubspec.yaml
. To wtyczka Flutter służąca do znajdowania często używanych lokalizacji w systemie plików.
W wierszu poleceń uruchom następujące polecenie:
$ flutter pub add path_provider
Aby wczytać zasób, musimy określić ścieżkę do niego w tabeli pubspec.yaml
. W pliku 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ć zasoby do projektu:
- W folderze głównym projektu utwórz nowy katalog o nazwie
assets
. - W folderze
assets
utwórz nowy Katalog o nazwiewww
. - W folderze
www
utwórz nowy Katalog o nazwiestyles
. - 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 w pliku 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 przypadku pliku style.css użyj kilku poniższych wierszy, aby ustawić styl nagłówka HTML:
h1 {
color: blue;
}
Gdy zasoby są gotowe i są gotowe do użycia, możesz wdrożyć metody niezbędne do wczytywania i wyświetlania zasobów, plików i ciągów HTML w technologii Flutter.
Wczytaj zasób Flutter
Aby wczytać utworzony przed chwilą zasób, musisz tylko wywołać metodę loadFlutterAsset
za pomocą metody WebViewController
i podać jako parametr ścieżkę do zasobu. Na końcu kodu dodaj następującą metodę:
lib/src/menu.dart
Future<void> _onLoadFlutterAssetExample(
WebViewController controller, BuildContext context) async {
await controller.loadFlutterAsset('assets/www/index.html');
}
Wczytaj plik lokalny
Aby wczytać plik na urządzeniu, możesz dodać metodę, która będzie korzystała z metody loadFile
, ponownie używając metody WebViewController
, która przyjmuje String
ze ścieżką do pliku.
Najpierw musisz utworzyć plik zawierający kod HTML. Wystarczy, że dodasz kod HTML w postaci ciągu na początku kodu w pliku menu.dart
tuż pod elementami importu.
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ć obiekt File
i zapisać w pliku ciąg HTML ciąg, musisz dodać 2 metody. _onLoadLocalFileExample
wczyta plik, podając ścieżkę w postaci ciągu znaków zwracanego przez metodę _prepareLocalFile()
. Dodaj do kodu te metody:
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 całkiem proste. WebViewController
zawiera metodę o nazwie loadHtmlString
, w której możesz podać ciąg HTML jako argument. W elemencie WebView
zostanie wyświetlona podana strona HTML. Dodaj do kodu następującą metodę:
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.
Dodaj pozycje menu
Gdy zasoby są już skonfigurowane i gotowe do użycia, a metody ze wszystkimi funkcjami są gotowe, więc możesz 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 zasobów, pliku i ciągu HTML
Aby sprawdzić, czy kod zadziałał przed chwilą, możesz uruchomić go na urządzeniu i kliknąć jedną z nowo dodanych pozycji menu. Zwróć uwagę, że _onLoadFlutterAssetExample
używa dodanego przez nas elementu style.css
, aby zmienić kolor nagłówka pliku HTML na niebieski.
13. Wszystko gotowe
Gratulacje! Ćwiczenie z programowania zostało ukończone. Ukończony kod tego ćwiczenia z programowania znajdziesz w repozytorium ćwiczeń z programowania.
Aby dowiedzieć się więcej, wykonaj inne ćwiczenia z programowania Flutter.