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