How i can create a dynamic list of stream?
I need a function that build a stream list and return the stream.
My fuction (try to return a value like this List<Stream<'dynamic'>>
:
List<Stream<dynamic>> _buildStreamList() async* {
var chat_overview_object = await query_chat_overview_id();
List<Stream<dynamic>> stream_list = [];
for (var i = 0; i < chat_overview_object.length; i ) {
stream_list.add(
FirebaseFirestore.instance
.collection('chat')
.doc('chat_overview_data')
.collection('data')
.doc('chat_overview_id_1234')
.snapshots(),
);
}
yield stream_list;
}
Why i need this?
I try to build a streambuilder with multiple stream on documents.
The streams can be different (for example one stream look at collection a document a other on collection b document b).
The example work but only with fix streams. Now i would like implement a **function which return a dynamic list of streams.
Here my code:
var _data = [];
typedef MultiStreamWidgetBuilder<T> = Widget Function(BuildContext context);
// A widget that basically re-calls its builder whenever any of the streams
// has an event.
class MultiStreamBuilder extends StatefulWidget {
const MultiStreamBuilder({
required this.streams,
required this.builder,
Key? key,
}) : super(key: key);
final List<Stream<dynamic>> streams;
final MultiStreamWidgetBuilder builder;
Widget build(BuildContext context) => builder(context);
@override
State<MultiStreamBuilder> createState() => _MultiStreamBuilderState();
}
class _MultiStreamBuilderState extends State<MultiStreamBuilder> {
final List<StreamSubscription<dynamic>> _subscriptions = [];
@override
void initState() {
super.initState();
_subscribe();
}
@override
void didUpdateWidget(MultiStreamBuilder oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.streams != widget.streams) {
// Unsubscribe from all the removed streams and subscribe to all the added ones.
// Just unsubscribe all and then resubscribe. In theory we could only
// unsubscribe from the removed streams and subscribe from the added streams
// but then we'd have to keep the set of streams we're subscribed to too.
// This should happen infrequently enough that I don't think it matters.
_unsubscribe();
_subscribe();
}
}
@override
Widget build(BuildContext context) => widget.build(context);
@override
void dispose() {
_unsubscribe();
super.dispose();
}
void _subscribe() {
for (final s in widget.streams) {
final subscription = s.listen(
(dynamic data) {
setState(() {
_data.add(data);
print('data: ' _data.toString());
});
},
one rror: (Object error, StackTrace stackTrace) {
setState(() {});
},
onDone: () {
setState(() {});
},
);
_subscriptions.add(subscription);
}
}
void _unsubscribe() {
for (final s in _subscriptions) {
s.cancel();
}
_subscriptions.clear();
}
}
class AppWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiStreamBuilder(
streams: _buildStreamList(),
[
FirebaseFirestore.instance
.collection('chat')
.doc('chat_overview_data')
.collection('data')
.doc('chat_overview_id_1233')
.snapshots(),
FirebaseFirestore.instance
.collection('chat')
.doc('chat_overview_data')
.collection('data')
.doc('chat_overview_id_1234')
.snapshots(),
FirebaseFirestore.instance
.collection('chat')
.doc('chat_overview_data')
.collection('data')
.doc('chat_overview_id_1232')
.snapshots()
],
builder: _buildMain,
);
}
}
Widget _buildMain(BuildContext context) {
return MaterialApp(
home: Row(
children: [
Text(
'_data: ' _data.toString(),
style: TextStyle(fontSize: 15),
)
],
));
}
Edit!
After a while i got a solution:
//multi stream widget builder
typedef MultiStreamWidgetBuilder<T> = Widget Function(BuildContext context);
// widget that basically re-calls its builder whenever any of the streams has an event.
class MultiStreamBuilder extends StatefulWidget {
const MultiStreamBuilder({
required this.streams,
required this.builder,
Key? key,
}) : super(key: key);
final List<Stream<dynamic>> streams;
final MultiStreamWidgetBuilder builder;
Widget build(BuildContext context) => builder(context);
@override
State<MultiStreamBuilder> createState() => _MultiStreamBuilderState();
}
//multi streambuilder
class _MultiStreamBuilderState extends State<MultiStreamBuilder> {
final List<StreamSubscription<dynamic>> _subscriptions = [];
@override
void initState() {
super.initState();
_subscribe();
}
@override
void didUpdateWidget(MultiStreamBuilder oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.streams != widget.streams) {
// Unsubscribe from all the removed streams and subscribe to all the added ones.
// Just unsubscribe all and then resubscribe. In theory we could only
// unsubscribe from the removed streams and subscribe from the added streams
// but then we'd have to keep the set of streams we're subscribed to too.
// This should happen infrequently enough that I don't think it matters.
_unsubscribe();
_subscribe();
}
}
@override
Widget build(BuildContext context) => widget.build(context);
@override
void dispose() {
_unsubscribe();
super.dispose();
}
void _subscribe() {
for (final s in widget.streams) {
final subscription = s.listen(
(dynamic data) {
setState(() {
//check object contain id
var object = chat_overview_object.singleWhere(
(element) => element.chat_overview_id[0].trim() == data.id);
//add to object data
object.last_message_send = data['last_message_send'];
object.last_update = data['last_update'];
object.members_id = data['members_id'];
object.new_message = data['new_message'];
object.user_display_name = data['user_display_name'];
});
//set index
index ;
//check all data load
if (index == chat_overview_object.length) {
//set data has load
data_load.value = false;
}
},
one rror: (Object error, StackTrace stackTrace) {
setState(() {});
},
onDone: () {
setState(() {});
},
);
_subscriptions.add(subscription);
}
}
void _unsubscribe() {
for (final s in _subscriptions) {
s.cancel();
}
_subscriptions.clear();
}
}
CodePudding user response:
You are using a wrong approach: You should not build a separate stream
for each document
, then decide on which stream
you are using. Instead, you should build only 1 stream
that returns all those documents
you may need.
Then, inside your StreamBuilder
, you can apply your if
condition to decide which document
you need to use. This way, the document will always be up-to-date.
Make sure you apply the right where
clause to call the appropriate documents
from the database.
EDIT 1:
So what you need is known as a collectionGroup:
db.collectionGroup('data').where('yourField', isEqualTo: 'whatever').snapshots()