颤振,GridView中的“流已被侦听”错误

时间:2019-01-01 08:02:27

标签: gridview controller flutter

我正在做的是获取卡通列表并通过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();

1 个答案:

答案 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'],
    );
  }
}

演示

Demo