Flutter, ошибка "поток уже прослушан" в GridView

Что я делаю, так это получаю список мультфильмов и показываю их с помощью 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());
      },
    );
  }

Но ошибка «Поток уже прослушан» возникает постоянно, в чем проблема моего StreamController ??

Как я могу это исправить?

Трассировки стека

I/flutter (21411): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (21411): The following StateError was thrown building Expanded(flex: 1):
I/flutter (21411): Bad state: Stream has already been listened to.
I/flutter (21411):
I/flutter (21411): When the exception was thrown, this was the stack:
I/flutter (21411): #4  _StreamBuilderBaseState._subscribe (package: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(package: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();

что такое stacktrace (первые несколько кадров)?

pskink 01.01.2019 09:07

@pskink я добавил

baeharam 01.01.2019 09:10

как тебе твой StreamController?

pskink 01.01.2019 09:15

@pskink я добавил

baeharam 01.01.2019 09:19

так зачем тебе .asBroadcastStream()? удалите его, hot restart ваше приложение и посмотрите, что произойдет

pskink 01.01.2019 09:26

те же ошибки .....

baeharam 01.01.2019 09:27

ты делал hot restart? не hot reaload

pskink 01.01.2019 09:28

Понятно, но возникла другая проблема. Как мне использовать StreamBuilder в GridView? Сам GridView или элемент чего?

baeharam 01.01.2019 09:33

я понятия не имею, что ты имеешь в виду

pskink 01.01.2019 09:34

см. мой код выше

baeharam 01.01.2019 09:40
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
10
4 041
1

Ответы 1

Stream должен использовать широковещательную рассылку только в том случае, если один и тот же поток нужно прослушивать несколько раз.

т.е.

@override
Widget build(BuildContext context) {
  return Column(
    children: [
      Expanded(
        flex: 1,
        child: StreamBuilder()),
      Expanded(
        flex: 1,
        child: StreamBuilder()),
    ],
  );
}

Кажется, я не могу воспроизвести ту же ошибку в моей собственной реализации отображения изображений из сети в GridView.

Поток в приведенном ниже примере не должен использовать широковещательную рассылку для обновления потока, поскольку его прослушивает один клиент. GridView можно обновить, запустив RefreshIndicator onRefresh. Непрерывный вызов Future<T>().then((response) => StreamController.add(response)); в этом примере не вызывает ошибок Bad state: Stream has already been listened to..

Если вы можете предоставить полное минимальное воспроизведение, которое я могу запустить локально, я могу помочь проверить, что может вызывать ошибки, создаваемые 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

Другие вопросы по теме