I am new to Flutter and trying to work on a problem that involves loading a webpage using webview_flutter
plugin.
My goal is following
Until 1. and 2. are going on, I want to show a full page Loading ...
indicator, so that user waits until the entire page is loaded and JS executions are complete.
My attempt includes using showDialog()
, in addition to keeping a boolean state variable pageLoaded
. However, when I run the application, the showDialog()
works well until page content is loaded, however, the second dialog appears after that and stays on the main page forever.
I do not like this approach since I am making use of 2 loading screens as I do not know a better way to do this. Also, the second loading screen should go away once pageLoaded
is set to true
, however, that is not happening as well. This makes me feel that I am missing something fundamental here.
I recorded my attempt and uploaded on YouTube to share what I have right now.
My attempt is available on github at https://github.com/hhimanshu/webview_images_render_intercept/tree/h2/so.
The exact file that I am talking is main.dart
, and I have pasted the content here as well
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
void main() => runApp(const MaterialApp(home: PageLoadApp()));
class PageLoadApp extends StatefulWidget {
const PageLoadApp({Key? key}) : super(key: key);
@override
State<PageLoadApp> createState() => _PageLoadAppState();
}
class _PageLoadAppState extends State<PageLoadApp> {
bool pageLoaded = false;
void onPageLoaded() {
setState(() {
pageLoaded = true;
});
}
static Future<String> get _url async {
await Future.delayed(const Duration(seconds: 10));
return 'https://www.canada.ca/en/immigration-refugees-citizenship/corporate/publications-manuals/discover-canada/read-online/canadas-history.html';
}
@override
Widget build(BuildContext context) => Scaffold(
body: Scaffold(
body: SafeArea(
child: Center(
child: FutureBuilder(
future: _url,
builder: (BuildContext context, AsyncSnapshot snapshot) =>
snapshot.hasData
? WebViewWidget(
url: snapshot.data,
onPageLoaded: onPageLoaded,
pageLoaded: pageLoaded,
)
: const CircularProgressIndicator()),
),
),
),
);
}
class WebViewWidget extends StatefulWidget {
final String url;
final bool pageLoaded;
final Function onPageLoaded;
const WebViewWidget(
{required this.url,
required this.onPageLoaded,
required this.pageLoaded});
@override
_WebViewWidget createState() => _WebViewWidget();
}
class _WebViewWidget extends State<WebViewWidget> {
late WebView _webView;
final Completer<WebViewController> _controller =
Completer<WebViewController>();
Iterable<Future<void>> cleanupLoadedHtmlPage(WebViewController controller) {
const List<String> javascriptToExecute = [
'document.getElementById("wb-lng").hidden = true;',
'document.getElementById("wb-srch").hidden = true',
'document.getElementsByClassName("gcweb-menu")[0].hidden = true',
'document.getElementsByClassName("mwsalerts")[0].hidden = true',
'document.getElementById("wb-bc").hidden = true',
'document.getElementsByClassName("pagination")[0].hidden = true',
'document.getElementsByClassName("pagedetails")[0].hidden = true',
'document.getElementsByClassName("global-footer")[0].hidden = true'
];
return javascriptToExecute.map((js) {
print("removing $js");
return controller.runJavascript(js);
});
}
@override
void initState() {
super.initState();
_webView = WebView(
initialUrl: widget.url,
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
onProgress: (int progress) {
print('WebView is loading (progress : $progress%)');
},
onPageStarted: (String url) {
print('Page started loading: $url');
},
onPageFinished: (String url) async {
if (!widget.pageLoaded) {
print("Showing Alert dialog");
showDialog(
context: context,
builder: (BuildContext context) {
return const AlertDialog(
title: Text('Loading ...'),
backgroundColor: Colors.red,
);
});
} else {
Navigator.pop(context);
}
print('Page finished loading: $url');
final controller = await _controller.future;
var cleanupFuture = cleanupLoadedHtmlPage(controller);
Future.wait(cleanupFuture).then((value) {
print("Clean up done => $value");
widget.onPageLoaded();
});
},
);
}
@override
void dispose() {
super.dispose();
//_webView = null;
}
@override
Widget build(BuildContext context) => _webView;
}
I am seeking help to achieve my goal and learn how to do the following
Additionally, while step 1. and 2. are running a user should see a Loading ...
indicator which cannot be dismissed. The indicator goes away automatically at step 3.
Please help me understand how to achieve this.
Thank you in advance
You can create a global variable isLoading
and use the help of ValueNotifier
and ValueListenableBuilder
to check if the value is loading to show a CircularProgressIndicator()
if it is.
I've refactored your code.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
void main() => runApp(const MaterialApp(home: PageLoadApp()));
final isLoading = ValueNotifier<bool>(true);
class PageLoadApp extends StatefulWidget {
const PageLoadApp({Key? key}) : super(key: key);
@override
State<PageLoadApp> createState() => _PageLoadAppState();
}
class _PageLoadAppState extends State<PageLoadApp> {
static String get _url {
// await Future.delayed(const Duration(seconds: 10));
return 'https://www.canada.ca/en/immigration-refugees-citizenship/corporate/publications-manuals/discover-canada/read-online/canadas-history.html';
}
@override
Widget build(BuildContext context) => Scaffold(
body: Scaffold(
body: ValueListenableBuilder<bool>(
valueListenable: isLoading,
builder: (context, value, _) {
return Scaffold(
body: Stack(
children: <Widget>[
WebViewWidget(
url: _url,
),
(value == true
? Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
)
: Container()),
],
),
);
;
}),
));
}
class WebViewWidget extends StatefulWidget {
final String url;
const WebViewWidget({
required this.url,
});
@override
_WebViewWidget createState() => _WebViewWidget();
}
class _WebViewWidget extends State<WebViewWidget> {
late WebView _webView;
final Completer<WebViewController> _controller =
Completer<WebViewController>();
Iterable<Future<void>> cleanupLoadedHtmlPage(WebViewController controller) {
const List<String> javascriptToExecute = [
'document.getElementById("wb-lng").hidden = true;',
'document.getElementById("wb-srch").hidden = true',
'document.getElementsByClassName("gcweb-menu")[0].hidden = true',
'document.getElementsByClassName("mwsalerts")[0].hidden = true',
'document.getElementById("wb-bc").hidden = true',
'document.getElementsByClassName("pagination")[0].hidden = true',
'document.getElementsByClassName("pagedetails")[0].hidden = true',
'document.getElementsByClassName("global-footer")[0].hidden = true'
];
return javascriptToExecute.map((js) {
print("removing $js");
return controller.runJavascript(js);
});
}
@override
void initState() {
super.initState();
_webView = WebView(
initialUrl: widget.url,
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
onProgress: (int progress) {},
onPageStarted: (String url) {
isLoading.value = true;
print('Page started loading: $url');
},
onPageFinished: (String url) async {
// Navigator.pop(context);
print('Page finished loading: $url');
final controller = await _controller.future;
var cleanupFuture = cleanupLoadedHtmlPage(controller);
Future.wait(cleanupFuture).then((value) {
print("Clean up done => $value");
});
// for demo purposes, wait for 3 seconds
await Future.delayed(Duration(seconds: 3));
isLoading.value = false;
},
);
}
@override
void dispose() {
super.dispose();
//_webView = null;
}
@override
Widget build(BuildContext context) => _webView;
}