I'm trying to pass Stream from a tab to another tab but when I comeback in the home.dart May I should close/destroy the Stream when tab changed? When the app runs the data are fetched correctly and everything is good.The problem appears only when I change tab. The data are stored to a firestore database.
I get this error:
Bad state: Stream has already been listened to
Here my Home.dart
class HomePage extends StatefulWidget {
HomePage({Key key}) : super(key: key);
Home createState() => Home();
}
class Home extends State<HomePage> {
int _currentIndex;
var _tabs = [];
List<Company> currentCompaniesList = List();
StreamController<List<Company>> _streamController;
Stream<List<Company>> companiesStream;
_getData() async {
_companyService
.getCompanies()
.then((value) => _streamController.sink.add(value));
}
@override
void initState() {
super.initState();
_currentIndex = 0;
_streamController = StreamController<List<Company>>();
_getData();
companiesStream = _streamController.stream;
}
}
@override
Widget build(BuildContext context) {
_tabs = [
CompanyTab(stream: companiesStream),
MapTab(),
Center(child: Text('Profile')),
Center(child: Text('Settings')),
];
return Scaffold(
...
actions: ...,
body: _tabs[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
backgroundColor: BACKGROUND_COLOR,
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
...
),
BottomNavigationBarItem(
...
),
BottomNavigationBarItem(
...
),
BottomNavigationBarItem(
...
)
],
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
),
);
}
}
Here my CompanyTab.dart
class CompanyTab extends StatefulWidget {
Stream stream;
CompanyTab({Key key, this.stream}) : super(key: key);
@override
_CompanyTabState createState() => _CompanyTabState(stream);
}
class _CompanyTabState extends State<CompanyTab> {
Stream stream;
_CompanyTabState(this.stream);
@override
void initState() {
super.initState();
}
StreamBuilder companyList() {
return StreamBuilder<List<Company>>(
initialData: [],
stream: stream,
builder: (BuildContext context, AsyncSnapshot<List<Company>> snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.connectionState == ConnectionState.waiting ||
snapshot.connectionState == ConnectionState.none ||
snapshot.data == null) {
return LoadingWidget();
} else {
return ListView.builder(
padding: const EdgeInsets.all(10),
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
Company company = snapshot.data.elementAt(index);
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 1.0, horizontal: 4.0),
child: Card(
child: ListTile(
onTap: () {},
title: Text(company.name),
...
),
),
),
);
});
}
});
}
@override
Widget build(BuildContext context) {
return Center(
child: Container(
child: companyList(),
),
);
}
}
It's about widgets lifecycle. I can suggest you two options.
1. Move _streamController and _getData() method to _CompanyTabState.
By default
BottomNavigationBar
destroys tab when you go away from one and init it again when you return back to it. If it's desired behaviour you need to move_streamController
and_getData()
method into_CompanyTabState
. Don't forget to call_streamController.close()
insidedispose()
method of_CompanyTabState
, it's important._companyService
can be injected into_CompanyTabState
. It's a matter of it's life time. Should work like this:2. Use IndexedStack
You can save tab's state and widget data (like scroll offset, entered text etc.) when you go away from the tab. It's iOS UITabBarController-like behaviour. Use IndexedStack to achieve this:
What option to use is up to you, you can use both if you want. But I would strongly recommend to move _streamController to _CompanyTabState as their lifecycles should be the same.