skip to Main Content

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 InAppWebViews 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 (1×1 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 1×1-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?

2

Answers


  1. I do not see that natively supported by Flutter.

    And the ability to preload and maintain inactive tabs is accomplished by executing JavaScript and rendering HTML/CSS in a separate thread or process. This is generally outside the scope of what a WebView is designed to do. WebView is simply an embeddable browser provided by the OS, and its capabilities are limited by the OS and the device. For example, on Android, WebView is backed by a version of Chrome, and on iOS, it is backed by Safari (WKWebView).

    Even a package like webview_flutter would not really help for your specific use case. Like with the flutter_inappwebview package, webview_flutter also requires that the WebView is part of the widget tree and visible on the screen to load a webpage. This is because the WebView is a native platform view that is embedded in your Flutter app.

    When you are trying to take a screenshot of a WebView or keep it in memory while it is not visible on the screen, you’re running into limitations of how platform views are handled in Flutter and how WebViews are implemented on the native platforms (Android and iOS).

    Login or Signup to reply.
  2. 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));
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search