pichillilorenzo / flutter_inappwebview

A Flutter plugin that allows you to add an inline webview, to use a headless webview, and to open an in-app browser window.
https://inappwebview.dev
Apache License 2.0
3.26k stars 1.6k forks source link

Headless inappwebview very slow on first use #2361

Open britsy83 opened 19 hours ago

britsy83 commented 19 hours ago

Is there an existing issue for this?

Current Behavior

I am using a headless inappwebview in my Flutterflow application. Upon the first call to the headless webview (after a new app launch for example), the process is much longer than the subsequent requests and I cannot figure out why.

Expected Behavior

I am expecting the plugin to have a more constant performance.

Steps with code example to reproduce

Steps with code example to reproduce ```dart // Automatic FlutterFlow imports import '/backend/backend.dart'; import '/actions/actions.dart' as action_blocks; import '/flutter_flow/flutter_flow_theme.dart'; import '/flutter_flow/flutter_flow_util.dart'; import '/custom_code/actions/index.dart'; // Imports other custom actions import '/flutter_flow/custom_functions.dart'; // Imports custom functions import 'package:flutter/material.dart'; // Begin custom action code // DO NOT REMOVE OR MODIFY THE CODE ABOVE! import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'dart:async'; Future fetchBookList(String? title, String? author) async { // Stop the function if both title and author are empty. // Normally covered by the UI through Form validation but this is to be safe. if ((title?.isEmpty ?? true) && (author?.isEmpty ?? true)) { return { 'error': 'Empty query', 'message': 'Please enter more specific search criteria.' }; } // Define the target URL String targetUrl = "https://www.arbookfind.com/advanced.aspx"; // For the webView to know the loop to execute since each click will generate a new onLoadStop bool hasClickedSubmitSearch = false; // Create a Completer to signal when the operations are complete Completer completer = Completer(); // Create a headless webview var headlessWebView = HeadlessInAppWebView( initialUrlRequest: URLRequest(url: WebUri(targetUrl)), onWebViewCreated: (controller) { controller.addJavaScriptHandler( handlerName: "bookAR", callback: (args) { // List to hold book details List> bookInfos = []; // Check if the argument is a Map and contains books if (args.isNotEmpty && args[0] is Map) { var books = args[0]['books']; if (books is List) { for (var book in books) { if (book is Map) { bookInfos.add(book); // Add each book JSON to the list } } } } // Determine if results exist String result = bookInfos.isNotEmpty ? 'yes' : 'no'; // Signal that the operations are complete with the correct structure completer.complete({'results': result, 'books': bookInfos}); }, ); }, onLoadStop: (controller, url) async { if (url != null && !url.toString().contains("usertype")) { if (!hasClickedSubmitSearch) { await controller.evaluateJavascript( source: """ var searchBookTitle = document.getElementById('ctl00_ContentPlaceHolder1_txtTitle'); if (searchBookTitle) { searchBookTitle.value = '$title'; } var searchBookAuthor = document.getElementById('ctl00_ContentPlaceHolder1_txtAuthor'); if (searchBookAuthor) { searchBookAuthor.value = '$author'; } var submitSearch = document.getElementById('ctl00_ContentPlaceHolder1_btnDoIt'); if (submitSearch) { submitSearch.click(); } """, ); hasClickedSubmitSearch = true; } else { await controller.evaluateJavascript( source: """ var maxList = 20 // Set the maximum number of elements to process var bookInfos = []; // Query all elements with id="Table6" var tables = document.querySelectorAll('#Table6'); // Loop through up to maxList elements for (var i = 0; i < Math.min(tables.length, maxList); i++) { var table = tables[i]; // Retrieve the book title var titleElement = table.querySelector('a#book-title'); var bookTitle = titleElement ? titleElement.innerText.trim() : ''; // Retrieve the book URL var urlElement = table.querySelector('a#book-title'); var bookPartialUrl = urlElement ? urlElement.getAttribute('href') : ''; // Append the base URL to the partial bookUrl var bookUrl = 'https://www.arbookfind.com/' + bookPartialUrl; // Extract the bookQuizNumber (the integer after 'q=') var quizNumberElement = bookUrl.split('q=')[1]?.split('&')[0]; var bookQuizNumber = quizNumberElement ? quizNumberElement : ''; // Extract the bookQuizLanguage (the value after 'l=') var quizLanguageElement = bookUrl.split('l=')[1]?.split('&')[0]; var bookQuizLanguage = quizLanguageElement ? quizLanguageElement : ''; // Retrieve the image path var imgElement = table.querySelector('img.book-cover-image'); var bookImg = imgElement ? imgElement.getAttribute('src') : ''; // Extract book author from the first

tag, before the
var authorElement = table.querySelector('p'); var bookAuthor = authorElement ? authorElement.innerHTML.split('
')[0] : ''; // Retrieve the AR points var pointsElement = table.querySelectorAll('strong')[2]; // This is the third tag var bookPoints = pointsElement ? pointsElement.innerText : ''; // Retrieve the ATOS level var atosElement = table.querySelectorAll('strong')[1]; // This is the second tag var bookAtos = atosElement ? atosElement.innerText : ''; // Create the bookInfo object with the extracted data var bookInfo = { 'book_title': bookTitle, 'book_author': bookAuthor, 'book_points': bookPoints, 'book_atos': bookAtos, 'book_img': bookImg, 'book_url': bookUrl, 'book_quiz_number': bookQuizNumber, 'book_quiz_language': bookQuizLanguage }; // Add the bookInfo to the bookInfos array bookInfos.push(bookInfo); } // Send the result to the Flutter handler window.flutter_inappwebview.callHandler('bookAR', {'books': bookInfos}); """, ); } } else { await controller.evaluateJavascript( source: """ var radioButton = document.getElementById('radParent'); if (radioButton) { radioButton.click(); } var submitButton = document.getElementById('btnSubmitUserType'); if (submitButton) { submitButton.click(); } """, ); } }, ); // Run the headless webview await headlessWebView.run(); // Add timeout logic try { var result = await completer.future.timeout( Duration(seconds: 15), onTimeout: () { return { 'error': 'Timeout', 'message': 'The operation took too long to complete.', 'book_title': '$title', 'book_author': '$author' }; }, ); // Dispose of the headless WebView after successful completion await headlessWebView.dispose(); // Clean up resources return result; } on TimeoutException catch (_) { // Ensure the WebView is disposed of in case of timeout await headlessWebView.dispose(); // Clean up resources return { 'error': 'Timeout', 'message': 'The operation took too long to complete.', 'book_title': '$title', 'book_author': '$author' }; } } ```

Stacktrace/Logs

Stacktrace/Logs ``` ```

Flutter version

v3.22.0

Operating System, Device-specific and/or Tool

iOS

Plugin version

v6.1.5

Additional information

No response

Self grab

  • [X] I'm ready to work on this issue!
pichillilorenzo commented 19 hours ago

Probably it could be related on web caching and not related to this plugin. You can try to set the cache mode, for example on Android you can set a custom InAppWebView cache mode setting: https://pub.dev/documentation/flutter_inappwebview/latest/flutter_inappwebview/InAppWebViewSettings/cacheMode.html and for iOS you can try to set the URLRequest cache Policy parameter: https://pub.dev/documentation/flutter_inappwebview/latest/flutter_inappwebview/URLRequest/cachePolicy.html

In any case, you should use an API (REST or whatever) to do what you are doing here for best performance. If there isn’t any API available other then doing web scraping, then you should expect possible delays as you are loading a full web page under the hood, with all assets etc.

britsy83 commented 16 hours ago

Thanks for the quick feedback. Unfortunately, there is no API so web scraping is my only option. The performance is not bad overall, except as I said when I initiate the web view for the first time.

Sorry for my ignorance, but where am I supposed to set this cache mode?

Thanks!

On Oct 20, 2024, at 11:52 AM, Lorenzo Pichilli @.***> wrote:

Probably it could be related on web caching and not related to this plugin. You can try to set the cache mode, for example on Android you can set a custom InAppWebView cache mode setting: https://pub.dev/documentation/flutter_inappwebview/latest/flutter_inappwebview/InAppWebViewSettings/cacheMode.html and for iOS you can try to set the URLRequest cache Policy parameter: https://pub.dev/documentation/flutter_inappwebview/latest/flutter_inappwebview/URLRequest/cachePolicy.html

In any case, you should use an API (REST or whatever) to do what you are doing here for best performance. If there isn’t any API available other then doing web scraping, then you should expect possible delays as you are loading a full web page under the hood, with all assets etc.

— Reply to this email directly, view it on GitHub https://github.com/pichillilorenzo/flutter_inappwebview/issues/2361#issuecomment-2425059991, or unsubscribe https://github.com/notifications/unsubscribe-auth/AWYTRFXW3CZUQFJ23OEJTVTZ4PGVJAVCNFSM6AAAAABQIT3F2CVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDIMRVGA2TSOJZGE. You are receiving this because you authored the thread.