Reputation: 2998
I’m trying to make a Flutter app that lets the user open up preloaded webpages from a list. The home screen should have a list of online articles with image previews (client-side screenshots) of them, and clicking on a list item should open the webpage in a WebView
or an InAppWebView
for the user to read.
None of that works because I couldn’t figure out how to pre-render webpages in order to (1) take screenshots of them without displaying them and (2) make the pages show up instantly, without having to load, when the user wants to view them.
My attempted solution was to use a non-widget class to instantiate InAppWebView
instances, take screenshot of their pages when they finishes loading, and store the InAppWebView
widgets in a List
to persist the widgets so they can be retrieved at any time. This method fails because InAppWebView
s don’t load pages unless they’re in the widget tree and visible on the screen. The screenshots never get taken because the WebViews never load.
Bad solution attempt:
import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:screenshot/screenshot.dart'; class WebView { static final Map globalInstancesPool = {}; static void dumpPool() { globalInstancesPool.clear(); } late final InAppWebView widget; Image? previewScreenshot; void _takeScreenshot(Function? callback) { ScreenshotController screenshotController = ScreenshotController(); screenshotController .captureFromWidget( Positioned(top: 0, left: 0, right: 0, bottom: 0, child: widget)) .then((Uint8List captureBytes) { previewScreenshot = Image.memory(captureBytes); if (callback == null) return; callback(previewScreenshot!); }); } WebView({required String url, Function? onScreenshotReady}) { widget = InAppWebView( initialUrlRequest: URLRequest(url: Uri.tryParse(url)), initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( cacheEnabled: true, clearCache: false, )), onLoadStop: (a, b) { _takeScreenshot(onScreenshotReady); }, ); } factory WebView.newOrExisting( {required String url, Function? onScreenshotReady}) { if (globalInstancesPool.containsKey(url)) { return globalInstancesPool[url]!; } WebView newWebViewInstance = WebView(url: url, onScreenshotReady: onScreenshotReady); globalInstancesPool[url] = newWebViewInstance; return newWebViewInstance; } }
I tried inserting all the WebViews into Offstage
widgets, but they don’t load. The closest I got to making it work is to just display the WebViews and size them very small (1x1 pixel). I want to avoid this technique, mainly for performance reasons.
I’ve read GitHub issues that ask about the same thing and haven’t found any working solutions other than the 1x1-pixel WebView trick:
https://github.com/pichillilorenzo/flutter_inappwebview/issues/1081
https://github.com/flutter/flutter/issues/64402
https://github.com/flutter/flutter/issues/51875
A better solution I’d like (but have no idea how to do) is to store the pre-rendered pages themselves without having a bunch of WebView widgets. So it’s just 1 WebView, and the pre-rendered pages get “injected” into the WebView. Thus far, I’ve yet to find any tutorials for doing this in Flutter.
This task may seem obscure but it’s just what browsers do:
When you open a link in a new tab in Chrome, the page gets loaded and rendered in the background.
Your inactive browser tabs persist in memory, enabling them to display immediately when you switch to them.
Some browsers, such as iOS Safari, show previews of tabs’ contents. Just like the screenshot previews I want to implement in my app.
All I want to do is preload multiple webpages and have them ready to use. Functionality that every major browser has. How do you accomplish this in Flutter?
Upvotes: 3
Views: 1746
Reputation:
you could try by wraping the webview within a
RepaintBoundary(key: key)
make sure to assign a unique key so you can call
RenderObject? object = key.currentContext?.findRenderObject();
and then call
RenderRepaintBoundary boundary = object as RenderRepaintBoundary;
ui.Image image = await boundary.toImage();
ByteData? byteData =
await image.toByteData(format: ui.ImageByteFormat.png);
var bytes = byteData!.buffer.asUint8List();
when the onPageFinished: (String url) {}, is called from the webview controller from the navigation delegate
controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onProgress: onPageProgress,
onPageStarted: (String url) {},
onPageFinished: (String url) {},
onWebResourceError: (WebResourceError error) async {
await Future.delayed(Durations.d50);
popAndShowError();
},
onNavigationRequest: (NavigationRequest request) {
return NavigationDecision.navigate;
},
),
)
..loadRequest(Uri.parse(uri));
Upvotes: 1