【问题标题】:Stream is not re-rendering when switching tabs on flutter在颤动上切换选项卡时流不会重新渲染
【发布时间】:2019-07-12 16:29:26
【问题描述】:

我有一个流生成器,它显示来自服务器的“帖子”列表。我已经使用 BLoC 架构来实现这一点。 但由于某种原因,当我切换标签并返回帖子消失时,我怎样才能防止帖子消失或让它们重新呈现?以下是我认为相关的一小部分代码,我可以添加更多如果需要:

选项卡 UI(并非所有代码,包含 BLoC 的文件在顶部导入):

  @override
  void initState() {
   bloc.fetchMyPosts();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 2,
      child: Scaffold(
        appBar: AppBar(
          title: Text("Posts", style: Style.appBarStyle),
          bottom: TabBar(
            tabs: [
              Tab(
                text: "My Posts",
              ),
              Tab(
                text: "My Other Posts",
              ),
            ],
          ),
        ),
        body: TabBarView(
          children: [
            Posts(stream: bloc.myPosts), //Stream builder with SliverChildBuilderDelegate
            Posts(stream:bloc.myOtherPosts),//Stream builder with SliverChildBuilderDelegate
          ],
        ),
      ),
    );
  }

流生成器(帖子):

Widget Posts({Stream stream, //Other variables}) {
  return StreamBuilder(
      stream:stream,
      builder: (BuildContext context, AsyncSnapshot snapshot) {
        switch(snapshot.connectionState) {
          case ConnectionState.none:
            return Row(
              children: <Widget>[
                Flexible(
                  child: Text("Please check if you are connected to the internet"),
                ),
              ],
            );
            break;
          case ConnectionState.waiting:
            if (snapshot.data == null){
              return Container(
                  color: Color(0xFFF4F4FF),
                  child: Container(child:Center(child:Text(variable?"Text one":"Text two"))));
            } else return Column(
              mainAxisSize: MainAxisSize.max,
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Center(
                  child: CircularProgressIndicator(),
                ),
                Center(
                  child: Text("Loading"),
                ),
              ],
            );
            break;
          case ConnectionState.active:
          case ConnectionState.done:
            if (snapshot.hasData) {
              return Container(
                  color:Colors.white,
                  child: CustomScrollView(
                    scrollDirection: Axis.vertical,
                    shrinkWrap: false,
                    slivers: <Widget>[
                      SliverPadding(
                        padding: const EdgeInsets.symmetric(vertical: 24.0),
                        sliver: SliverList(
                          delegate: SliverChildBuilderDelegate(
                                (context, index) => PostCard(post:snapshot.data[index],//variables),
                            childCount: snapshot.data.length,
                          ),
                        ),
                      )
                    ],
                  ));
            }
            if (snapshot.data == null){
              return Container(
                  color: Color(0xFFF4F4FF),
                  child: Container(child:Center(child:Text(variable?"Text one":"Text two"))));
            }
        }
      });
}

BLoC:

class Bloc{

  ApiClient _client = ApiClient();

  final _myPosts = BehaviourSubject<List<Post>>();
  final _myOtherPosts = BehaviourSubject<List<Post>>();

  Stream<List<Post>> get myPosts => _myPosts.stream;
   Stream<List<Post>> get myOtherPosts => _myOtherPosts.stream;

  fetchMyPosts() async {
    List<Post> posts = await _client.getMyPosts();
    _myPosts.sink.add(posts);
  }

  fetchMyOtherPosts() async {
    List<Post> posts = await _client.getMyOtherPosts();
    _myOtherPosts.sink.add(posts);
  }


  dispose(){
    _myPosts.close();
     _myOtherPosts.close();
  }

}

final bloc = Bloc();

主屏幕:

class MainScreen extends StatefulWidget {
  UserBloc userBloc;

  MainScreen({this.userBloc});

  @override
  _MainScreenState createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {


  int _currentIndex = 0;

  onTabTapped(int index) {
    setState(() {
      _currentIndex = index;
    });
  }

  Widget getPage(int index) {
    if (index == 0) {
      return PostPage(myHandle: widget.userBloc.userValue);
    }
    if (index == 1) {
      return PageOne();
    }
    if (index == 3) {
      return  PageTwo();
    }
    if (index == 4) {
      return PageThree(userBloc: widget.userBloc);
    }

    return PostPage(userBloc: widget.userBloc);
  }

  Widget customNav() {
    return Container(
        color: Colors.white,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: <Widget>[
            IconButton(
                icon: Icon(Icons.library_books),
                onPressed: () => setState(() {
                  _currentIndex = 0;
                })),
            // MORE ICONS but similar code
          ],
        ));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Stack(children: <Widget>[
          getPage(_currentIndex),
          Positioned(
            bottom: 0.0,
            left: 0.0,
            right: 0.0,
            child: customNav(),
          ),
        ]));
  }
}

【问题讨论】:

  • 这是一个小提示,但在 initState 覆盖中,您可能应该首先调用 super.initState,然后才能执行您的自定义操作
  • @AlexanderArendar 会做,从我在某处读到的内容认为没关系。

标签: dart flutter


【解决方案1】:

我将PublishSubject 更改为BehaviourSubject,它似乎有效。我也将它与 Marcos Boaventura 的回答结合使用。虽然我使用了两个流构建器。

PublishSubject:开始为空,只发出新元素。

BehaviorSubject:它需要一个初始值并将其或最新的元素重播给新订阅者。

【讨论】:

    【解决方案2】:

    看看这段代码,我放了一些 cmets。我可以使用 streambuilder 和 bloc 模式来模拟未来延迟的异步数据获取。此小部件正在运行,但您需要适应您的需求。

    class TabWidget extends StatefulWidget {
      @override
      _TabWidgetState createState() => _TabWidgetState();
    }
    
    class _TabWidgetState extends State<TabWidget> with SingleTickerProviderStateMixin {
    
      Bloc _bloc;
    
      @override
      void initState() {
        super.initState();
        _bloc = Bloc(); // can be your bloc.fetchData();
      }
    
      @override
      void dispose() {
        _bloc?.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        //i really recomment using stream builder to create all layout
        // if length property is dynamic
        return DefaultTabController(
          length: 2,
          child: Scaffold(
            appBar: AppBar(
              title: Text("Tab screen"),
              bottom: TabBar(
                tabs: [
                  Tab( text: "My Posts" ),
                  Tab( text: "Other" ),
                ],
              ),
            ),
    
            body: StreamBuilder<List<Widget>>(
                stream: _bloc.getTabData,
                builder: (context, asyncSnapshot){
                  switch(asyncSnapshot.connectionState){
                    case ConnectionState.none:
                      return Row(
                        children: <Widget>[
                          Flexible(
                            child: Text("handle none state here, this is because i am simulate a async event"),
                          ),
                        ],
                      );
                      break;
    
                    case ConnectionState.waiting:
                      return Column(
                        mainAxisSize: MainAxisSize.max,
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: <Widget>[
                          Center(
                            child: CircularProgressIndicator(),
                          ),
                          Center(
                            child: Text("Loading data..."),
                          ),
                        ],
                      );
                      break;
    
                    case ConnectionState.active:
                    case ConnectionState.done:
                       //assuming that snapshot has valid data...
                      return TabBarView(
                        children:[
                          asyncSnapshot.data[0],
                          asyncSnapshot.data[1],
                        ],
                      );
                  }
                }
            ),
          ),
        );
      }
    
    }
    
    class Bloc{
      // post items
      // just to simulate data
      List<Widget> _tabList1 = List.generate(10, (index){ return Text("TAB 1 Item $index");} );
      List<Widget> _tabList2 = List.generate(10, (index){ return Text("TAB 2 Item $index");} );
    
      //tab's data stream
      PublishSubject< List<Widget>> _tabData = PublishSubject();
      Observable<List<Widget>> get getTabData => _tabData.stream;
    
      Bloc() {
        Future.delayed(Duration(seconds: 5), () {
          List<Widget> tabDataWidgets = List();
          // adding tab's data
          tabDataWidgets.add( ListView(
            children: _tabList1,
          ) );
    
          tabDataWidgets.add( ListView(
            children: _tabList2,
          ) );
    
          _addingToSink( tabDataWidgets );
        });
      }
    
      void _addingToSink( final List<Widget> list) => _tabData.sink.add( list );
    
      dispose(){ _tabData?.close(); }
    }
    

    【讨论】:

    • 谢谢,我实际上为每个选项卡使用了两个流构建器,也许这就是它们不重新渲染的原因。每个人都在使用 SliverChildBuilderDelegate,因此可能更难将它们放入一个流构建器中,但我会看到。
    • 我使用您提到的内容更新了我的流构建器,但仍将其拆分为两个流构建器,我使用 BehaviourSubject 而不是 PublishSubject 并且它似乎有效,但它一直落入 ConnectionState.waiting 情况下没有数据我怎么能阻止这个?
    • 可能是因为当没有数据并且流侦听器仍在等待时,您没有向流接收器添加任何内容。尝试添加以接收一个空数据列表甚至 null 并在 connectionState active 或 done 中处理它
    • 伙计,我真的需要查看更多代码来向您提供我对此的看法。可能是整个屏幕代码,对不起....
    • 问题可能出在你的 bloc 类中...我真的需要查看更多代码
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-06
    • 1970-01-01
    • 2021-09-12
    • 1970-01-01
    • 2019-03-12
    • 2021-03-31
    • 2020-07-30
    相关资源
    最近更新 更多