Stream is not re-rendering when switching tabs on flutter

Issue

I have a stream builder that shows a list of “posts” from a server. I have used the BLoC architecture to accomplish this. But for some reason when I switch tabs and back the posts disappear how can I keep the posts from disappearing or have them re-render? Below is small part of my code I think is relevant I can add more if needed:

Tab UI (not all the code, file containing BLoC is imported at top):

  @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
          ],
        ),
      ),
    );
  }

Stream Builder (Posts):

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();

Main Screen:

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(),
          ),
        ]));
  }
}

Solution

Take a look at this code i put some comments. I can do this using streambuilder and bloc pattern simulating an async data fetching with future delayed. This widget is working but you will need adapt to your needs.

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(); }
}

Answered By – Marcos Boaventura

Answer Checked By – Gilberto Lyons (FlutterFixes Admin)

Leave a Reply

Your email address will not be published.