Home > Net >  Flutter Firebase create a dynamic list of streams
Flutter Firebase create a dynamic list of streams

Time:10-28

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()
  • Related