Flutter ImageStream Bad state: Future already completed
1,662
If the ImageStream emits more than once you will call completer.complete()
twice, which is an error. As per the ImageStream
documentation this can happen if the image is animating, or if the resource is changed.
If you only care about the first emit you can await image.image.resolve(ImageConfiguration()).first
. A more hacky solution would be to call complete()
only if completer.isCompleted == false
.
Author by
Chris
Updated on January 04, 2023Comments
-
Chris over 1 year
I have an
WebView
inside my app where I crawl the current Website.This is the procedure:
- User taps on button
- crawl the content of the current URL
- get all the images
- for each image get its dimension
- print out first three elements of sorted List
The Problem is 4:
This is my code for it:
Future<Size> _calculateImageDimension(String imageUrl) { Completer<Size> completer = Completer(); Image image = Image.network(imageUrl); image.image.resolve(ImageConfiguration()).addListener( ImageStreamListener( (ImageInfo image, bool synchronousCall) { var myImage = image.image; Size size = Size(myImage.width.toDouble(), myImage.height.toDouble()); completer.complete(size); // <- StateError }, ), ); return completer.future; }
This fails with:
Bad state: Future already completed
Now the weird part is that it only fails on some URL's.
What is wrong with my
_calculateImageDimension
? What am I missing?This is the complete Code:
import 'package:boilerplate/ui/shared_widgets/buttons/rounded_corner_text_button.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:webview_flutter/webview_flutter.dart'; import 'dart:async'; import 'package:http/http.dart' as http; import 'package:html/parser.dart' as parser; import 'package:html/dom.dart' as dom; class WebViewExample extends StatefulWidget { @override _WebViewExampleState createState() => _WebViewExampleState(); } class _WebViewExampleState extends State<WebViewExample> { // Reference to webview controller late WebViewController _controller; final _stopwatch = Stopwatch(); String _currentUrl = ''; List<ImageWithSize> _imagesWithSize = []; bool _isLoading = true; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Flutter Web View Example'), ), body: SafeArea( child: Column( children: [ Expanded( child: WebView( initialUrl: 'https://www.prettylittlething.com/recycled-green-towelling-oversized-beach-shirt.html', javascriptMode: JavascriptMode.unrestricted, onWebViewCreated: (WebViewController webViewController) { // Get reference to WebView controller to access it globally _controller = webViewController; }, javascriptChannels: <JavascriptChannel>{ // Set Javascript Channel to WebView _extractDataJSChannel(context), }, onPageStarted: (String url) { setState(() { _isLoading = true; }); }, onPageFinished: (String url) { setState(() { _imagesWithSize = []; _currentUrl = url; _isLoading = false; }); }, ), ), RoundedCornersTextButton( title: 'GET', isEnabled: !_isLoading, onTap: () { _getData(); }), ], ), ), ); } JavascriptChannel _extractDataJSChannel(BuildContext context) { return JavascriptChannel( name: 'Flutter', onMessageReceived: (JavascriptMessage message) { String pageBody = message.message; }, ); } void _getData() async { // print(url); _stopwatch.start(); final response = await http.get(Uri.parse(_currentUrl)); final host = Uri.parse(_currentUrl).host; dom.Document document = parser.parse(response.body); final elements = document.getElementsByTagName("img").toList(); for (var element in elements) { var imageSource = element.attributes['src'] ?? ''; bool validURL = Uri.parse(imageSource).host == '' || Uri.parse(host + imageSource).host == '' ? false : true; if (validURL && !imageSource.endsWith('svg')) { Uri imageSourceUrl = Uri.parse(imageSource); if (imageSourceUrl.host.isEmpty) { imageSource = host + imageSource; } if (_imagesWithSize.firstWhereOrNull( (element) => element.imageUrl == imageSource, ) == null) { Size size = await _calculateImageDimension(imageSource); _imagesWithSize.add( ImageWithSize( imageSource, size, ), ); } } } _imagesWithSize.sort( (a, b) => (b.imageSize.height * b.imageSize.width).compareTo( a.imageSize.height * a.imageSize.width, ), ); print(_imagesWithSize.first.imageUrl); print(_imagesWithSize[1].imageUrl); print(_imagesWithSize[2].imageUrl); _stopwatch.stop(); print('executed in ${_stopwatch.elapsed}'); } } Future<Size> _calculateImageDimension(String imageUrl) { Completer<Size> completer = Completer(); Image image = Image.network(imageUrl); image.image.resolve(ImageConfiguration()).addListener( ImageStreamListener( (ImageInfo image, bool synchronousCall) { var myImage = image.image; Size size = Size(myImage.width.toDouble(), myImage.height.toDouble()); completer.complete(size); }, ), ); return completer.future; } class ImageWithSize { final String imageUrl; final Size imageSize; ImageWithSize(this.imageUrl, this.imageSize); }
-
Chris about 2 yearsI didnt know my images where chaning or animating but your hacky solution is working! Tbh I don't really understand the
_calculateImageDimension
, I just copied it. So feel free to elaborate what exactly happens here :D Anyway: Thanks for your help!