错误状态:在颤动中从 addStream 添加项目时,您无法关闭主题

Posted

技术标签:

【中文标题】错误状态:在颤动中从 addStream 添加项目时,您无法关闭主题【英文标题】:Bad state: You cannot close the subject while items are being added from addStream in flutter 【发布时间】:2019-02-10 23:20:08 【问题描述】:

我正在使用 RxDart 来观察变化并相应地更新 UI。当应用程序启动时,我正在进行网络调用并成功获取数据,观察更改并相应地更新 UI。但是当我在关闭屏幕时处理Subjects 时。它给出以下错误:

══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (15524): The following StateError was thrown while finalizing the widget tree:
I/flutter (15524): Bad state: You cannot close the subject while items are being added from addStream

这里是 bloc 类:

class MovieDetailBloc 
  final _repository = Repository();
  final _movieId = PublishSubject<int>();
  final _trailers = BehaviorSubject<Future<TrailerModel>>();

  Function(int) get fetchTrailersById => _movieId.sink.add;
  Observable<Future<TrailerModel>> get movieTrailers => _trailers.stream;

  MovieDetailBloc() 
    _movieId.stream.transform(_itemTransformer()).pipe(_trailers);
  

  dispose() 
    _movieId.close();
    _trailers.close();
  

  _itemTransformer() 
    return ScanStreamTransformer(
      (Future<TrailerModel> trailer, int id, int index) 
        print(index);
        trailer = _repository.fetchTrailers(id);
        return trailer;
      ,
    );
  

这是我称之为的 UI 屏幕:

import 'dart:async';

import 'package:flutter/material.dart';
import '../blocs/movie_detail_bloc_provider.dart';
import '../models/trailer_model.dart';

class MovieDetail extends StatefulWidget 
  final posterUrl;
  final description;
  final releaseDate;
  final String title;
  final String voteAverage;
  final int movieId;

  MovieDetail(
    this.title,
    this.posterUrl,
    this.description,
    this.releaseDate,
    this.voteAverage,
    this.movieId,
  );

  @override
  State<StatefulWidget> createState() 
    return MovieDetailState(
      title: title,
      posterUrl: posterUrl,
      description: description,
      releaseDate: releaseDate,
      voteAverage: voteAverage,
      movieId: movieId,
    );
  


class MovieDetailState extends State<MovieDetail> 
  final posterUrl;
  final description;
  final releaseDate;
  final String title;
  final String voteAverage;
  final int movieId;

  MovieDetailBloc bloc;

  MovieDetailState(
    this.title,
    this.posterUrl,
    this.description,
    this.releaseDate,
    this.voteAverage,
    this.movieId,
  );

  @override
  void didChangeDependencies() 
    bloc = MovieDetailBlocProvider.of(context);
    bloc.fetchTrailersById(movieId);
    super.didChangeDependencies();

  

  @override
  void dispose() 
    bloc.dispose();
    super.dispose();
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      body: NestedScrollView(
        headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) 
          return <Widget>[
            SliverAppBar(
              expandedHeight: 220.0,
              floating: false,
              pinned: false,
              flexibleSpace: FlexibleSpaceBar(
                  background: Image.network(
                "https://image.tmdb.org/t/p/w500$posterUrl",
                fit: BoxFit.cover,
              )),
            ),
          ];
        ,
        body: Padding(
          padding: const EdgeInsets.all(10.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Container(margin: EdgeInsets.only(top: 5.0)),
              Text(
                title,
                style: TextStyle(
                  fontSize: 25.0,
                  fontWeight: FontWeight.bold,
                ),
              ),
              Container(margin: EdgeInsets.only(top: 8.0, bottom: 8.0)),
              Row(
                children: <Widget>[
                  Icon(
                    Icons.favorite,
                    color: Colors.red,
                  ),
                  Container(
                    margin: EdgeInsets.only(left: 1.0, right: 1.0),
                  ),
                  Text(
                    voteAverage,
                    style: TextStyle(
                      fontSize: 18.0,
                    ),
                  ),
                  Container(
                    margin: EdgeInsets.only(left: 10.0, right: 10.0),
                  ),
                  Text(
                    releaseDate,
                    style: TextStyle(
                      fontSize: 18.0,
                    ),
                  ),
                ],
              ),
              Container(margin: EdgeInsets.only(top: 8.0, bottom: 8.0)),
              Text(description),
              Container(margin: EdgeInsets.only(top: 8.0, bottom: 8.0)),
              Text(
                "Trailer",
                style: TextStyle(
                  fontSize: 25.0,
                  fontWeight: FontWeight.bold,
                ),
              ),
              Container(margin: EdgeInsets.only(top: 8.0, bottom: 8.0)),
              StreamBuilder(
                stream: bloc.movieTrailers,
                builder:
                    (context, AsyncSnapshot<Future<TrailerModel>> snapshot) 
                  if (snapshot.hasData) 
                    return FutureBuilder(
                      future: snapshot.data,
                      builder:
                          (context, AsyncSnapshot<TrailerModel> itemSnapShot) 
                        if (itemSnapShot.hasData) 
                          if (itemSnapShot.data.results.length > 0)
                            return trailerLayout(itemSnapShot.data);
                          else
                            return noTrailer(itemSnapShot.data);
                         else 
                          return CircularProgressIndicator();
                        
                      ,
                    );
                   else 
                    return CircularProgressIndicator();
                  
                ,
              ),
            ],
          ),
        ),
      ),
    );
  

  Widget noTrailer(TrailerModel data) 
    return Center(
      child: Container(
        child: Text("No trailer available"),
      ),
    );
  


Widget trailerLayout(TrailerModel data) 
  return Row(
    children: <Widget>[
      Expanded(
        child: Column(
          children: <Widget>[
            Container(
              margin: EdgeInsets.all(5.0),
              height: 100.0,
              color: Colors.grey,
            ),
            Text(
              data.results[0].name,
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
            ),
          ],
        ),
      ),
      Expanded(
        child: Column(
          children: <Widget>[
            Container(
              margin: EdgeInsets.all(5.0),
              height: 100.0,
              color: Colors.grey,
            ),
            Text(
              data.results[1].name,
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
            ),
          ],
        ),
      ),
    ],
  );

当我关闭屏幕时出现错误。 dispose 方法抛出上述异常。我该如何解决这个问题?

【问题讨论】:

【参考方案1】:

你也可以通过替换来解决:

MovieDetailBloc() 
  _movieId.stream.transform(_itemTransformer()).pipe(_trailers);

MovieDetailBloc() 
  _movieId.stream.transform(_itemTransformer()).listen((data) 
    if (!_categories.isClosed) 
      _trailers.add(data);
    
  );

【讨论】:

【参考方案2】:

我认为排干流应该可以解决问题

  dispose() async 
    _movieId.close();
    await _trailers.drain();
    _trailers.close();
  

https://api.dartlang.org/stable/2.0.0/dart-async/Stream-class.html

【讨论】:

哇!它刚刚奏效。太感谢了。我不能在 2 分钟内接受你的回答。过段时间我会做的。 哇它好用。但为什么会发生呢?难道不应该发生吗?如何预防? @rxlky 我不确定你的问题是什么。这就是流的工作方式。一些更突出的文档可能会更好。 谢谢!我不知道“drain”会丢弃流中的所有数据:)

以上是关于错误状态:在颤动中从 addStream 添加项目时,您无法关闭主题的主要内容,如果未能解决你的问题,请参考以下文章

如何在颤动中从父小部件访问所有孩子的状态?

如何在颤动中从tab1(不是重建tab2)更改tab2的状态

在颤动中从 TextFormField 添加多个值

在颤动中从列表中添加和删除对象

在颤动中从地图<日期时间,列表>中删除项目

如何在颤动中从父小部件调用子小部件的initState()方法