我正在做的是获取卡通列表并通过GridView显示。下面的代码正在获取数据
Future<void> _getWebtoonData() async {
var response;
if(_daysReceivedResponse[_pressedButtonDayIndex]){
response = _daysResponse[_pressedButtonDayIndex];
} else {
response= await http.get('https://comic.naver.com/webtoon/weekdayList.nhn?week='+_currentWebtoonAddress);
_daysReceivedResponse[_pressedButtonDayIndex] = true;
_daysResponse[_pressedButtonDayIndex] = response;
}
dom.Document document = parser.parse(response.body);
final e1 = document.querySelectorAll('.img_list .thumb');
final e2 = document.querySelectorAll('.img_list .desc');
final e3 = document.querySelectorAll('.img_list .rating_type');
List<List<String>> infoCollection = List<List<String>>();
List<String> info = List<String>();
for(int i=0; i<e1.length; i++){
info.add(e1[i].getElementsByTagName('img')[0].attributes['src']);
info.add(e1[i].getElementsByTagName('a')[0].attributes['title']);
info.add(e2[i].getElementsByTagName('a')[0].innerHtml);
info.add(e3[i].getElementsByTagName('strong')[0].innerHtml);
infoCollection.add(info);
}
_controller.sink.add(infoCollection);
}
我正在通过GridView显示以下图像,标题,艺术家和评分
Widget _getWebtoonGridView() {
return StreamBuilder(
stream: _controller.stream.asBroadcastStream(),
builder: (BuildContext context, AsyncSnapshot<List> snapshot){
if(snapshot.hasError)
print(snapshot.error);
else if(snapshot.hasData){
return GridView.count(
crossAxisCount: 3,
childAspectRatio: 0.6,
children: List.generate(snapshot.data.length, (index){
return _getWebtoonInfo(index, snapshot.data[index]);
}),
);
}
else if(snapshot.connectionState != ConnectionState.done)
return Center(child: CircularProgressIndicator());
},
);
}
但是“ Stream已经被监听”错误不断发生,我的StreamController出了什么问题?
我该如何解决?
StackTrace
I / flutter(21411):══╡小组件库的例外情况提示 ╞═════════════════════════════════════════════════ ══════════我/扑 (21411):在构建Expanded时引发了以下StateError(flex: 1):I / flutter(21411):错误状态:流已被侦听。 I / flutter(21411):I / flutter(21411):引发异常时, 这是堆栈:I / flutter(21411):#4
_StreamBuilderBaseState._subscribe(包:flutter / src / widgets / async.dart:135:37)I / flutter(21411):#5 _StreamBuilderBaseState.initState(package:flutter / src / widgets / async.dart:109:5)I / flutter(21411):#6
StatefulElement._firstBuild (包:flutter / src / widgets / framework.dart:3830:58)I / flutter (21411):#7 ComponentElement.mount (package:flutter / src / widgets / framework.dart:3696:5)I / flutter(21411):8 Element.inflateWidget(package:flutter / src / widgets / framework.dart:2950:14)I / flutter
(21411):#9 Element.updateChild (package:flutter / src / widgets / framework.dart:2753:12)
StreamController变量
StreamController<List<List<String>>> _controller = StreamController<List<List<String>>>.broadcast();
答案 0 :(得分:0)
仅在必须多次收听同一流的情况下,流才需要使用广播。
即
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
flex: 1,
child: StreamBuilder()),
Expanded(
flex: 1,
child: StreamBuilder()),
],
);
}
我似乎无法在自己的实现中在GridView上显示来自网络的图像时复制相同的错误。
下面的示例中的Stream不需要使用Broadcast来刷新Stream,因为单个客户端正在监听它。可以通过触发RefreshIndicator onRefresh
刷新GridView。在此示例中,对Future<T>().then((response) => StreamController.add(response));
的连续调用不会导致Bad state: Stream has already been listened to.
错误。
如果您可以提供我可以在本地运行的完整的最小化repro,我可以帮助检查是什么导致了Stream引发的错误。
这里有您可以尝试的完整示例。拉页面刷新并重置分页,然后滚动到页面底部以加载下一张图像。样本中显示的图像是从https://jsonplaceholder.typicode.com/photos
获取的import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var _streamController = StreamController<List<Album>>();
var _scrollController = ScrollController();
// GridView has 3 columns set
// Succeeding pages should display in rows of 3 for uniformity
loadMoreImages(bool increment) {
setState(() {
if(!increment) _imageGridCursorEnd = 21;
else _imageGridCursorEnd += 21;
});
}
// Call to fetch images
// if refresh set to true, it will trigger setState() to reset the GridView
loadImages(bool refresh){
fetchAlbum().then((response) => _streamController.add(response));
if(refresh)loadMoreImages(!refresh); // refresh whole GridView
}
@override
void initState() {
super.initState();
loadImages(false);
_scrollController.addListener(() {
if (_scrollController.position.atEdge) {
if (_scrollController.position.pixels == 0)
print('Grid scroll at top');
else {
print('Grid scroll at bottom');
loadMoreImages(true);
}
}
});
}
@override
void dispose() {
super.dispose();
_streamController.close();
}
var _imageGridCursorStart = 0, _imageGridCursorEnd = 21;
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _streamController.stream,
builder: (BuildContext context, AsyncSnapshot<List<Album>> snapshot) {
if (snapshot.hasData) {
// This ensures that the cursor won't exceed List<Album> length
if (_imageGridCursorEnd > snapshot.data.length)
_imageGridCursorEnd = snapshot.data.length;
debugPrint('Stream snapshot contains ${snapshot.data.length} item/s');
}
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: RefreshIndicator(
// onRefresh is a RefreshCallback
// RefreshCallback is a Future Function().
onRefresh: () async => loadImages(true),
child: snapshot.hasData
? GridView.count(
physics: AlwaysScrollableScrollPhysics(),
controller: _scrollController,
primary: false,
padding: const EdgeInsets.all(20),
crossAxisSpacing: 10,
mainAxisSpacing: 10,
crossAxisCount: 3,
children: getListImg(snapshot.data
.getRange(_imageGridCursorStart, _imageGridCursorEnd)
.toList()),
)
: Text('Waiting...'),
),
),
);
},
);
}
Future<List<Album>> fetchAlbum() async {
final response =
await http.get('https://jsonplaceholder.typicode.com/photos');
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
Iterable iterableAlbum = json.decode(response.body);
var albumList = List<Album>();
List<Map<String, dynamic>>.from(iterableAlbum).map((Map model) {
// Add Album mapped from json to List<Album>
albumList.add(Album.fromJson(model));
}).toList();
return albumList;
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
getListImg(List<Album> listAlbum) {
final listImages = List<Widget>();
for (var album in listAlbum) {
listImages.add(
Container(
padding: const EdgeInsets.all(8),
child: Image.network(album.albumThumbUrl, fit: BoxFit.cover),
// child: Thumbnail(image: imagePath, size: Size(100, 100)),
),
);
}
return listImages;
}
}
class Album {
final int albumId;
final int id;
final String title;
final String albumImageUrl;
final String albumThumbUrl;
Album(
{this.albumId,
this.id,
this.title,
this.albumImageUrl,
this.albumThumbUrl});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
albumId: json['albumId'],
id: json['id'],
title: json['title'],
albumImageUrl: json['url'],
albumThumbUrl: json['thumbnailUrl'],
);
}
}
演示