Search code examples
flutterflutter-layout

Flutter: TabBar content not changing inside StreamBuilder


Code:

Widget _getLeases() {
    return StreamBuilder<QuerySnapshot>(
      stream: leasesStream,
      builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
        if (snapshot.hasError) return Text('Error: ${snapshot.error}');
        if (!snapshot.hasData) return Container();
        if (snapshot.data == null) return const Loader();

        leases = snapshot.data!.docs;
        checkLeasesProgress();

        return _showLeases();
      },
    );
  }

Widget _showLeases() {
    return Column(
      children: <Widget>[
        ListTile(
          tileColor: Colors.grey[200],
          title: Text(
            translate('leases.leases'),
            style: Helpers.headerTitleStyle,
          ),
          trailing: _optionSelected == "edit" ? _addLease() : null,
        ),
        Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            TabBar(
              isScrollable: true,
              controller: _leaseTabController,
              onTap: (int index) {
                setState(() {
                  _leaseTabController.animateTo(index);
                });
              },
              labelColor: Theme.of(context).primaryColor,
              unselectedLabelColor: Theme.of(context).primaryColor,
              indicator: UnderlineTabIndicator(
                borderSide: BorderSide(
                  color: Theme.of(context).primaryColor,
                ),
              ),
              tabs: [
                Tab(text: translate('leases.finished')),
                Tab(text: translate('leases.in_progress')),
                Tab(text: translate('leases.not_started')),
              ],
            ),
            Padding(
              padding: const EdgeInsets.only(top: 8.0),
              child: Column(
                children: [
                  Visibility(
                    visible: _leaseTabController.index == 0,
                    child: leasesList(leasesTab['finished']),
                  ),
                  Visibility(
                    visible: _leaseTabController.index == 1,
                    child: leasesList(leasesTab['inProgress']),
                  ),
                  Visibility(
                    visible: _leaseTabController.index == 2,
                    child: leasesList(leasesTab['notStarted']),
                  ),
                ],
              ),
            ),
          ],
        ),
      ],
    );
  }

Everything is rendered fine but when I try to change tab it doesn't do anything. I was using FutureBuilder before and it was working fine.

Edit: after removing setState from onTap I can see tabs being changed but content remains the same with same tab index. This seems to work, but not with the controller:

TabBar(
                  // controller: _leaseTabController,
                  tabs: [
                   Tab(text: translate('leases.finished')),
                   Tab(text: translate('leases.in_progress')),
                   Tab(text: translate('leases.not_started')),
                  ],
                ),

Solution

  • Trick was creating another StatefulWidget to have its own state:

    import 'package:flutter/material.dart';
    import 'package:flutter/widgets.dart';
    
    class OptionsScreen extends StatefulWidget {
      const OptionsScreen({Key? key}) : super(key: key);
      @override
      OptionsScreenState createState() => OptionsScreenState();
    }
    
    class OptionsScreenState extends State<OptionsScreen> {
      final bodyGlobalKey = GlobalKey();
      late var streamFunction;
    
      @override
      void initState() {
        streamFunction = delayFunction;
        super.initState();
      }
    
      @override
      void dispose() {
        super.dispose();
      }
    
      Stream<int> delayFunction = (() async* {
        for (int i = 1; i <= 3; i++) {
          await Future<void>.delayed(const Duration(seconds: 1));
          yield i;
        }
      })();
    
      @override
      Widget build(BuildContext context) {
        return StreamBuilder<int>(
          stream: streamFunction,
          builder: (BuildContext context, snapshot) {
            if (snapshot.hasError) return Text('Error: ${snapshot.error}');
            if (!snapshot.hasData) return const Text('loading...');
            if (snapshot.data == null) return Container();
    
            // return tabs();
            return const CustomTabs();
          },
        );
      }
    }
    
    class CustomTabs extends StatefulWidget {
      const CustomTabs({Key? key}) : super(key: key);
    
      @override
      State<CustomTabs> createState() => _CustomTabsState();
    }
    
    class _CustomTabsState extends State<CustomTabs>
        with SingleTickerProviderStateMixin {
      late TabController _tabController;
    
      @override
      void initState() {
        _tabController = TabController(length: 3, vsync: this);
        super.initState();
      }
    
      @override
      void dispose() {
        _tabController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Column(
          children: <Widget>[
            Column(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                TabBar(
                  isScrollable: true,
                  controller: _tabController,
                  // COMMENT FOR WAY B
                  onTap: (int index) {
                    setState(() {
                      _tabController.animateTo(index);
                    });
                  },
                  labelColor: Theme.of(context).primaryColor,
                  indicator: UnderlineTabIndicator(
                    borderSide: BorderSide(
                      color: Theme.of(context).primaryColor,
                    ),
                  ),
                  tabs: [
                    Tab(text: '1'),
                    Tab(text: '2'),
                    Tab(text: '3'),
                  ],
                ),
                // WAY A
                // THIS WAY HAS NO ANIMATION BUT IT HAS DYNAMIC HEIGHT
                // SetState TRIGGERS STREAMBUILDER WITH TAB CHANGES
                Column(
                  children: [
                    Visibility(
                      visible: _tabController.index == 0,
                      child: const Text('1111'),
                    ),
                    Visibility(
                      visible: _tabController.index == 1,
                      child: Column(
                        children: const [
                          Text('2222'),
                          Text('2222'),
                          Text('2222'),
                          Text('2222'),
                          Text('2222'),
                          Text('2222'),
                        ],
                      ),
                    ),
                    Visibility(
                      visible: _tabController.index == 2,
                      child: const Text('33333'),
                    ),
                    const Text('This is a different widget. Tabs will push it down')
                  ],
                ),
                // THIS WAY HAS NO DYNAMIC HEIGHT BUT IT HAS ANIMATION
                // STREAMBUILDER ONLY TRIGGERS FIRST TIME
                // Container(
                //   color: Colors.red,
                //   height: 200,
                //   padding: const EdgeInsets.only(top: 8.0),
                //   child: TabBarView(
                //     controller: _tabController,
                //     children: [
                //       const Text('1111'),
                //       Column(
                //         children: const [
                //           Text('2222'),
                //           Text('2222'),
                //           Text('2222'),
                //           Text('2222'),
                //         ],
                //       ),
                //       const Text('3333'),
                //     ],
                //   ),
                // ),
              ],
            ),
          ],
        );
      }
    }