Flutter ImageStream 错误状态:未来已经完成
Flutter ImageStream Bad state: Future already completed
我的应用程序中有一个 WebView
,我可以在其中抓取当前网站。
这是程序:
- 用户点击按钮
- 抓取当前URL
的内容
- 获取所有图片
- 获取每个图像的尺寸
- 打印出排序列表的前三个元素
问题是4:
这是我的代码:
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;
}
这失败了:
Bad state: Future already completed
现在奇怪的是它只在某些 URL 上失败。
我的 _calculateImageDimension
怎么了?我错过了什么?
这是完整的代码:
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);
}
如果 ImageStream 发出多次,您将调用 completer.complete()
两次,这是一个错误。根据 ImageStream
文档,如果图像是动画的,或者如果资源被更改,就会发生这种情况。
如果您只关心第一个发射,您可以等待 image.image.resolve(ImageConfiguration()).first
。一个更 hacky 的解决方案是仅在 completer.isCompleted == false
.
时调用 complete()
我的应用程序中有一个 WebView
,我可以在其中抓取当前网站。
这是程序:
- 用户点击按钮
- 抓取当前URL 的内容
- 获取所有图片
- 获取每个图像的尺寸
- 打印出排序列表的前三个元素
问题是4:
这是我的代码:
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;
}
这失败了:
Bad state: Future already completed
现在奇怪的是它只在某些 URL 上失败。
我的 _calculateImageDimension
怎么了?我错过了什么?
这是完整的代码:
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);
}
如果 ImageStream 发出多次,您将调用 completer.complete()
两次,这是一个错误。根据 ImageStream
文档,如果图像是动画的,或者如果资源被更改,就会发生这种情况。
如果您只关心第一个发射,您可以等待 image.image.resolve(ImageConfiguration()).first
。一个更 hacky 的解决方案是仅在 completer.isCompleted == false
.
complete()